2009-08-03 8 views
3

Je voudrais faire un TreeView qui affiche des serveurs et des dossiers. Selon mes besoins, j'ai fait 2 classes:[C#] [WPF] Comment faire un TreeView asynchrone sans geler l'interface utilisateur?

- Dossier

class Folder 
{ 
    // Hidden attributes 
    public String ElementID { get; set; } 

    // Attributes displayed in the treeview 
    public String ElementName { get; set; } 

    // This collection is binded with the GUI defined in XAML 
    public CompositeCollection Children { get; set; } 

    public BitmapImage Image {get; set; } 

    // Constructor 
    public Folder() 
    { 
     // Fill the treeview with a temporary child as text 
     Children = new CompositeCollection(); 
     Children.Add(new TextBlock() 
     { 
      Text = "Loading...", 
      FontStyle = FontStyles.Italic 
     }); 
    } 

    // Fill the Children collection 
    public void LoadChildren() 
    { 
     // Clear the Children list 
     Children.Clear(); 

     // Populate the treeview thanks to the bind 
     foreach (Folder folder in this.GetChildren()) 
     { 
      Children.Add(folder); 
     } 
    } 

    // Get the Folder Children as Folder 
    protected List<Folder> GetChildren() 
    { 
     System.Threading.Thread.Sleep(1000); 

     List<Folder> resu = new List<Folder>(); 

     Folder f1 = new Folder(); 
     f1.ElementID = "1"; 
     f1.ElementName = "folder 1"; 

     Folder f2 = new Folder(); 
     f2.ElementID = "2"; 
     f2.ElementName = "folder 2"; 

     Folder f3 = new Folder(); 
     f3.ElementID = "3"; 
     f3.ElementName = "folder 3"; 

     Folder f4 = new Folder(); 
     f4.ElementID = "4"; 
     f4.ElementName = "folder 4"; 

     resu.Add(f1); 
     resu.Add(f2); 
     resu.Add(f3); 
     resu.Add(f4); 

     return resu; 
    } 

Thread.Sleep() simule que la méthode peut prendre un certain temps.

- Serveur: dossier

class Server : Folder 
{ 
    // Get the Servers list 
    public static List<Server> GetServers() 
    { 
     System.Threading.Thread.Sleep(1500); 

     // Create a list of Servers 
     List<Server> servers = new List<Server>(); 

     Server s1 = new Server(); 
     s1.ElementID = "1"; 
     s1.ElementName = "Server 1"; 

     Server s2 = new Server(); 
     s2.ElementID = "2"; 
     s2.ElementName = "Server 2"; 

     Server s3 = new Server(); 
     s3.ElementID = "3"; 
     s3.ElementName = "Server 3"; 

     Server s4 = new Server(); 
     s4.ElementID = "4"; 
     s4.ElementName = "Server 4"; 

     servers.Add(s1); 
     servers.Add(s2); 
     servers.Add(s3); 
     servers.Add(s4); 

     return servers; 
    } 
} 

Voici mon Code TreeView:

partie XAML:

<Window x:Class="WpfApplication1.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:ar="clr-namespace:WpfApplication1" 
DataContext="{Binding RelativeSource={RelativeSource Self}}" 
Title="Browser" Height="327" Width="250"> 
<Window.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type ar:Folder}" ItemsSource="{Binding Path=Children}" > 
     <StackPanel Orientation="Horizontal"> 
      <Image Source="Images\TreeView\folder.png" Height="15" Width="15" /> 
      <TextBlock Text="{Binding Path=ElementName}" /> 
     </StackPanel> 
    </HierarchicalDataTemplate> 
    <HierarchicalDataTemplate DataType="{x:Type ar:Server}" ItemsSource="{Binding Path=Children}"> 
     <StackPanel Orientation="Horizontal"> 
      <Image Source="Images\TreeView\server.png" Height="15" Width="15" /> 
      <TextBlock Text="{Binding Path=ElementName}" /> 
     </StackPanel> 
    </HierarchicalDataTemplate> 
</Window.Resources> 
<Grid> 
     <TreeView HorizontalAlignment="Stretch" Width="230" Name="treeView"> 
     <TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private"> 
       <TreeViewItem TextBlock.FontStyle="Italic" 
         Header="Loading..."/> 
     </TreeViewItem> 
    </TreeView>   
</Grid> 

code Derrière:

public partial class Window1 : Window 
{ 
    public Window1() 
    { 
     InitializeComponent(); 

     // Add an event in order to know when an TreeViewItem is Expanded 
     AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(treeItemExpanded), true); 
    } 

    // Event when a treeitem expands 
    private void treeItemExpanded(object sender, RoutedEventArgs e) 
    {   
     // Get the source 
     var item = e.OriginalSource as TreeViewItem; 

     // If the item source is a Simple TreeViewItem 
     if (item == null) 
      return; 

     if (item.Name == "root") 
     { 
      List<Server> servers = new List<Server>(); 

      servers = Server.GetServers(); 

      root.Items.Clear(); 

      // Fill the treeview with the servers 
      root.ItemsSource = servers;     
     }   

     // Get data from item as Folder (also works for Server) 
     var treeViewElement = item.DataContext as Folder; 

     // If there is no data 
     if (treeViewElement == null) 
      return; 

     // Load Children (populate the treeview) 
     ThreadPool.QueueUserWorkItem(delegate 
     { 
      Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate 
      { 
       treeViewElement.LoadChildren(); 

      }); 
     }); 
    } 
} 

Je voudrais faire l'interface utilisateur ne gèle tout en dépensant un treeView article (2 cas: racine dépensant, dépensant dossier)

Pour le moment 1- je ne vois pas le « Chargement en cours ... » quand je dépensons le nœud racine j'ai essayé quelque chose comme ça, mais il y a une exception: le fil doit être en mode STA:

// Load Children (populate the treeview) 
ThreadPool.QueueUserWorkItem(delegate 
{ 
    List<Server> servers = Server.GetServers(); 

    Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate 
    { 
     root.Items.Clear(); 

     // Fill the treeview with the servers 
     root.ItemsSource = servers; 
    }); 
}); 

2- quand un noeud est dépensé, je dois le « Chargement en cours ... "et après un certain temps, l'interface utilisateur est mise à jour. Pendant ce temps, l'interface utilisateur est gelée: l'utilisateur est incapable de déplacer la fenêtre.

Pourriez-vous m'aider s'il vous plaît?

(PS: Si vous avez d'autres commentaires, je serais heureux de les ici;))

Répondre

1

supprimer de la classe Dossier:

 Children.Add(new TextBlock() 
     { 
      Text = "Loading...", 
      FontStyle = FontStyles.Italic 
     }); 

Permettant de le faire:

  ThreadPool.QueueUserWorkItem(delegate 
      { 
       List<Server> servers = new List<Server>(); 

       servers = Server.GetServers(); 

       this.Dispatcher.Invoke((Action)delegate 
       { 
        item.Items.Clear(); 

        // Fill the treeview with the servers 
        item.ItemsSource = servers; 

       }); 
      }); 
3

Merci Gary, mais j'ai trouvé une autre solution qui permet de résoudre presque tout :)

Fisrt, je annonce d une classe vide CustomTreeViewITem

class CustomTreeViewItem 
{ } 

Cette classe est utilisée en XAML.

j'ai changé quelques petites choses dans la classe de dossier: - remplacer CompositeCollection à un ObservableCollection - faire le dossier hérite de CustomTreeViewItem - changer le constructeur

class Folder : CustomTreeViewItem 
{ 
    // Hidden attributes 
    public String ElementID { get; set; } 

    // Attributes displayed in the treeview 
    public String ElementName { get; set; } 

    public ObservableCollection<CustomTreeViewItem> Children { get; set; } // This collection is binded with the GUI defined in XAML 

    // Constructor 
    public Folder() 
    { 
     // Fill the treeview with a temporary child as text 
     Children = new ObservableCollection<CustomTreeViewItem>(); 
     Children.Add(new CustomTreeViewItem()); 
    } 

    // Get the Folder Children as Folder 
    // Method overriden by the Server class 
    public List<Folder> GetChildren() 
    { 
     System.Threading.Thread.Sleep(5000); 

     List<Folder> resu = new List<Folder>(); 

     Folder f1 = new Folder(); 
     f1.ElementID = "1"; 
     f1.ElementName = "folder 1"; 

     Folder f2 = new Folder(); 
     f2.ElementID = "2"; 
     f2.ElementName = "folder 2"; 

     Folder f3 = new Folder(); 
     f3.ElementID = "3"; 
     f3.ElementName = "folder 3"; 

     Folder f4 = new Folder(); 
     f4.ElementID = "4"; 
     f4.ElementName = "folder 4"; 

     resu.Add(f1); 
     resu.Add(f2); 
     resu.Add(f3); 
     resu.Add(f4); 

     return resu; 
    } 

je ne change pas la classe de serveur

maintenant mon XAML ressemble à ceci:

<Window x:Class="WpfApplication1.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:ar="clr-namespace:WpfApplication1" 
DataContext="{Binding RelativeSource={RelativeSource Self}}" 
Title="Browser" Height="327" Width="250"> 
<Window.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type ar:Folder}" ItemsSource="{Binding Path=Children}"> 
     <TextBlock Text="{Binding Path=ElementName}" /> 
    </HierarchicalDataTemplate> 
    <HierarchicalDataTemplate DataType="{x:Type ar:Server}" ItemsSource="{Binding Path=Children}"> 
     <TextBlock Text="{Binding Path=ElementName}" /> 
    </HierarchicalDataTemplate> 
    <HierarchicalDataTemplate DataType="{x:Type ar:CustomTreeViewItem}"> 
      <TextBlock Text="Loading..." /> 
    </HierarchicalDataTemplate> 
</Window.Resources> 
<Grid> 
    <TreeView HorizontalAlignment="Stretch" Width="230" Name="treeView"> 
     <TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private"> 
      <ar:CustomTreeViewItem/> 
     </TreeViewItem> 
    </TreeView> 
</Grid> 

Et mon code derrière:

private void treeItemExpanded(object sender, RoutedEventArgs e) 
    { 
     // Get the source 
     var item = e.OriginalSource as TreeViewItem; 

     // If the item source is a Simple TreeViewItem 
     if (item == null) 
     // then Nothing 
     { return; } 

     if (item.Name == "root") 
     { 
      // Load Children (populate the treeview) 
      ThreadPool.QueueUserWorkItem(delegate 
      { 
       List<Server> servers = Server.GetServers(); 

       Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate 
       { 
        root.Items.Clear(); 

        // Fill the treeview with the servers 
        root.ItemsSource = servers; 
       }); 
      }); 
     } 

     // Get data from item as Folder (also works for Server) 
     Folder treeViewElement = item.DataContext as Folder; 

     // If there is no data 
     if (treeViewElement == null) 
     { 
      return; 
     } 
     // Load Children (populate the treeview) 
     ThreadPool.QueueUserWorkItem(delegate 
     { 

      // Clear the Children list 
      var children = treeViewElement.GetChildren(); 

      // Populate the treeview thanks to the bind 

      Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate 
      { 
       treeViewElement.Children.Clear(); 

       foreach (Folder folder in children) 
       { 
        treeViewElement.Children.Add(folder); 
       } 

      }); 
     }); 
    } 

Il me permet de coutume chaque TreeViewClass (Personnalisé, dossier, serveur), grâce à HierarchicalDataTemplate.

0

Je pense qu'il a besoin de quelques corrections: Le XAML doit pointer vers la méthode d'événement expandeur. Je l'ai mis dans l'élément TreeViewItem.

<TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private" Expanded="treeItemExpanded"> 

Et le Code Beind se réfère à «root» à deux endroits. Je pense que ça devrait être "item".

root.Items.Clear(); 
// Fill the treeview with the servers 
root.ItemsSource = servers; 

devient

item.Items.Clear(); 
// Fill the treeview with the servers 
item.ItemsSource = servers; 

homme, je crois avoir trouvé ma mine d'or TreeView ici. Merci pour la direction !!!!!! J'ai réussi à faire en sorte que ce travail illustre bien le filetage dans TreeView AND HierarchicalDataTemplates AND ObservableCollections. Très agréable! DOUX!

Questions connexes