2013-02-20 1 views
2

지난 몇 개월 동안 TreeView를 많이 사용했는데 이제 UI 고정 문제가 발생합니다. 많은 양의 항목이 있고 해당 항목의 데이터 부분이 매우 빠르게 생성되지만 TreeViewItem을 작성하고 시각화하면 (UI 스레드에서 수행해야 함) 시간이 걸립니다.WPF UI 스레드가 TreeView의 항목 묶음로드시 고정되었습니다.

쉘 브라우저와 C : \ Windows \ System32 디렉토리를 예로 들어 보겠습니다. (나는 http://www.codeproject.com/Articles/24237/A-Multi-Threaded-WPF-TreeView-Explorer 해결책을 고쳤다.)이 디렉토리는 ~ 2500 개의 파일과 폴더를 가지고있다.

DataItem 및 Visual로드는 다른 스레드에서 구현되지만 파일 및 디렉토리 정보는 빨리 읽히지 않으므로 아무런 이점이 없습니다. TreeViewItem을 생성하고 표시 할 때 응용 프로그램이 멈 춥니 다. 나는 시도했다 : 항목을로드 할 때

  1. 는 UI 스레드에 대해 다른 DispatcherPriorities 설정 예를 들어 창 DispatcherPriority.ContextIdle 대화 형 (나는 그것을 움직일 수 있었다), 그러나 다음 항목은 정말로드 된 천천히 ..
  2. 사람들을로드하는 동안

내 목표는 응용 프로그램이 상호 작용 될 것을 .. 작성하고 100 개 번 당 항목,하지만 모자 어떤 혜택, UI 스레드가 여전히 동결 것처럼, 블록의 항목을 시각화 아이템이야! 현재이 문제를 해결하는 방법은 하나뿐입니다. 창 크기, 스크롤 막대 위치를 추적하고 시각적으로 보이는 항목 만로드하는 자체 컨트롤을 구현하는 방법이 있지만 그렇게하기는 쉽지 않습니다. 결국 성능이 더 좋을 것입니다 .. :)

어쩌면 누군가 시각적 인 항목을로드하는 동안 대화 형 응용 프로그램을 만드는 방법을 생각하고 있니?!

코드 :

완벽한 솔루션이 볼 수 있습니다 : http://www.speedyshare.com/hksN6/ShellBrowser.zip

프로그램 :

public partial class DemoWindow 
{ 
    public DemoWindow() 
    { 
     InitializeComponent(); 
     this.Loaded += DemoWindow_Loaded; 
    } 

    private readonly object _dummyNode = null; 

    delegate void LoaderDelegate(TreeViewItem tviLoad, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem);  
    delegate void AddSubItemDelegate(TreeViewItem tviParent, IEnumerable<ItemToAdd> itemsToAdd); 

    // Gets an IEnumerable for the items to load, in this sample it's either "GetFolders" or "GetDrives" 
    // RUNS ON: Background Thread 
    delegate IEnumerable<ItemToAdd> DEL_GetItems(string strParent); 

    void DemoWindow_Loaded(object sender, RoutedEventArgs e) 
    { 
     var tviRoot = new TreeViewItem(); 

     tviRoot.Header = "My Computer"; 
     tviRoot.Items.Add(_dummyNode); 
     tviRoot.Expanded += OnRootExpanded; 
     tviRoot.Collapsed += OnItemCollapsed; 
     TreeViewItemProps.SetItemImageName(tviRoot, @"Images/Computer.png"); 

     foldersTree.Items.Add(tviRoot); 
    } 

    void OnRootExpanded(object sender, RoutedEventArgs e) 
    { 
     var treeViewItem = e.OriginalSource as TreeViewItem; 

     StartItemLoading(treeViewItem, GetDrives, AddItem); 

    } 

    void OnItemCollapsed(object sender, RoutedEventArgs e) 
    { 
     var treeViewItem = e.OriginalSource as TreeViewItem; 

     if (treeViewItem != null) 
     { 
      treeViewItem.Items.Clear(); 
      treeViewItem.Items.Add(_dummyNode); 
     } 

    } 

    void OnFolderExpanded(object sender, RoutedEventArgs e) 
    { 
     var tviSender = e.OriginalSource as TreeViewItem; 

     e.Handled = true; 
     StartItemLoading(tviSender, GetFilesAndFolders, AddItem); 
    } 

    void StartItemLoading(TreeViewItem tviSender, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem) 
    { 
     tviSender.Items.Clear(); 

     LoaderDelegate actLoad = LoadSubItems; 

     actLoad.BeginInvoke(tviSender, tviSender.Tag as string, actGetItems, actAddSubItem, ProcessAsyncCallback, actLoad); 
    } 

    void LoadSubItems(TreeViewItem tviParent, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem) 
    { 
      var itemsList = actGetItems(strPath).ToList(); 

      Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList); 
    } 



    // Runs on Background thread. 
    IEnumerable<ItemToAdd> GetFilesAndFolders(string strParent) 
    { 
     var list = Directory.GetDirectories(strParent).Select(itemName => new ItemToAdd() {Path = itemName, TypeOfTheItem = ItemType.Directory}).ToList(); 

     list.AddRange(Directory.GetFiles(strParent).Select(itemName => new ItemToAdd() {Path = itemName, TypeOfTheItem = ItemType.File})); 

     return list; 
    } 

    // Runs on Background thread. 
    IEnumerable<ItemToAdd> GetDrives(string strParent) 
    { 
     return (Directory.GetLogicalDrives().Select(x => new ItemToAdd(){Path = x, TypeOfTheItem = ItemType.DiscDrive})); 
    } 

    void AddItem(TreeViewItem tviParent, IEnumerable<ItemToAdd> itemsToAdd) 
    { 
     string imgPath = ""; 

     foreach (ItemToAdd itemToAdd in itemsToAdd) 
     { 
      switch (itemToAdd.TypeOfTheItem) 
      { 
       case ItemType.File: 
        imgPath = @"Images/File.png"; 
        break; 
       case ItemType.Directory: 
        imgPath = @"Images/Folder.png"; 
        break; 
       case ItemType.DiscDrive: 
        imgPath = @"Images/DiskDrive.png"; 
        break; 
      } 

      if (itemToAdd.TypeOfTheItem == ItemType.Directory || itemToAdd.TypeOfTheItem == ItemType.File) 
       IntAddItem(tviParent, System.IO.Path.GetFileName(itemToAdd.Path), itemToAdd.Path, imgPath); 
      else 
       IntAddItem(tviParent, itemToAdd.Path, itemToAdd.Path, imgPath);     
     }    
    } 

    private void IntAddItem(TreeViewItem tviParent, string strName, string strTag, string strImageName) 
    { 
     var tviSubItem = new TreeViewItem(); 
     tviSubItem.Header = strName; 
     tviSubItem.Tag = strTag; 
     tviSubItem.Items.Add(_dummyNode); 
     tviSubItem.Expanded += OnFolderExpanded; 
     tviSubItem.Collapsed += OnItemCollapsed; 

     TreeViewItemProps.SetItemImageName(tviSubItem, strImageName); 

     tviParent.Items.Add(tviSubItem); 
    } 

    private void ProcessAsyncCallback(IAsyncResult iAR) 
    { 
     // Call end invoke on UI thread to process any exceptions, etc. 
     Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() => ProcessEndInvoke(iAR))); 
    } 

    private void ProcessEndInvoke(IAsyncResult iAR) 
    { 
     try 
     { 
      var actInvoked = (LoaderDelegate)iAR.AsyncState; 
      actInvoked.EndInvoke(iAR); 
     } 
     catch (Exception ex) 
     { 
      // Probably should check for useful inner exceptions 
      MessageBox.Show(string.Format("Error in ProcessEndInvoke\r\nException: {0}", ex.Message)); 
     } 
    } 

    private struct ItemToAdd 
    { 
     public string Path; 
     public ItemType TypeOfTheItem; 
    } 

    private enum ItemType 
    { 
     File, 
     Directory, 
     DiscDrive 
    } 
} 

public static class TreeViewItemProps 
{ 
    public static string GetItemImageName(DependencyObject obj) 
    { 
     return (string)obj.GetValue(ItemImageNameProperty); 
    } 

    public static void SetItemImageName(DependencyObject obj, string value) 
    { 
     obj.SetValue(ItemImageNameProperty, value); 
    } 

    public static readonly DependencyProperty ItemImageNameProperty; 

    static TreeViewItemProps() 
    { 
     ItemImageNameProperty = DependencyProperty.RegisterAttached("ItemImageName", typeof(string), typeof(TreeViewItemProps), new UIPropertyMetadata(string.Empty)); 
    } 
} 

XAML :

<Window x:Class="ThreadedWpfExplorer.DemoWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:ThreadedWpfExplorer" 
    Title="Threaded WPF Explorer" Height="840" Width="350" Icon="/ThreadedWpfExplorer;component/Images/Computer.png"> 
    <Grid> 
     <TreeView x:Name="foldersTree"> 
      <TreeView.Resources> 
       <Style TargetType="{x:Type TreeViewItem}"> 
        <Setter Property="HeaderTemplate"> 
         <Setter.Value> 
          <DataTemplate DataType="ContentPresenter"> 
           <Grid> 
            <StackPanel Name="spImg" Orientation="Horizontal"> 
             <Image Name="img" 
               Source="{Binding 
                  RelativeSource={RelativeSource 
                      Mode=FindAncestor, 
                      AncestorType={x:Type TreeViewItem}}, 
                      Path=(local:TreeViewItemProps.ItemImageName)}" 
               Width="20" Height="20" Stretch="Fill" VerticalAlignment="Center" /> 
             <TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center" /> 
            </StackPanel> 
           </Grid> 

          </DataTemplate> 
         </Setter.Value> 
        </Setter> 
       </Style> 
      </TreeView.Resources> 
     </TreeView> 
    </Grid> 
</Window> 
블록개

대체로드 항목 :

private const int rangeToAdd = 100; 

void LoadSubItems(TreeViewItem tviParent, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem) 
{ 
    var itemsList = actGetItems(strPath).ToList(); 


    int index; 
    for (index = 0; (index + rangeToAdd) <= itemsList.Count && rangeToAdd <= itemsList.Count; index = index + rangeToAdd) 
    { 
     Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList.GetRange(index, rangeToAdd)); 
    } 

    if (itemsList.Count < (index + rangeToAdd) || rangeToAdd > itemsList.Count) 
    { 
     var itemsLeftToAdd = itemsList.Count % rangeToAdd; 

     Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList.GetRange((rangeToAdd > itemsList.Count) ? index : index - rangeToAdd, itemsLeftToAdd)); 
    } 
} 

답변

3

당신이 찾고있는 것을 UI 가상화라고하며 여러 가지 다른 WPF 컨트롤에서 지원됩니다. 특히 TreeView와 관련하여 가상화를 설정하는 방법에 대한 자세한 내용은 this article을 참조하십시오.

이 기능을 활용하려면 ItemsSource 속성을 사용하고 코드에서 항목을 직접 추가하지 말고 컬렉션에서 항목을 제공해야합니다. 어쨌든이 작업을 수행하는 것이 좋지만 기존 코드로 기능을 재구성하려면 약간의 구조 조정이 필요할 수 있습니다.

+0

감사합니다. – TTT

+0

항목 컨테이너가 다시 사용되었지만 컨테이너의 속성이 항상 다시 설정되지는 않았으므로 VirtualizingStackPanel.VirtualizationMode = "Recycling"동작을 사용하는 ItemControl이 예기치 않은 경우가 있습니다. 내 경우 희생자는 IsExpanded 속성입니다. 스크롤 막대를 빠르게 드래그하면 확장 이벤트가 발생합니다. 예 : http://www.speedyshare.com/H9BTV/ShellSolution.zip – TTT

0

왜 당신의 관찰 모음을 만들고 XAML에서 바인딩하지?

MvvM 디자인 패턴을 확인하고 방금 클래스를 만들고 여기에 초기화에서부터 xaml을 가리키고 목록을 만든 다음 트리 뷰에 그 목록에 바인딩하여 귀하의 목록에있는 각 항목.

정보에 약간 부족하다는 것을 알고 있지만, MvvM을 수행하는 것은 정말 쉽고 stackoverflow를 통해 살펴보고 예제를 보게됩니다.

정말 모든 항목에 begininvoke를 호출 할 필요가 없습니다. 이것은 단지 mvvm 관점에서가 아니라 단지리스트에 바인딩하는 것입니다.

개체에도 색인 된 '수준'을 사용할 수 있습니다.

0

또 다른 유용한 기술은 데이터 가상화입니다. 감사합니다, 나는 VirtualizingStackPanel.IsVirtualizing = "True"가 기본 TreeView 동작에 있다고 생각했습니다. CodeProject에서 좋은 기사와 샘플 프로젝트가 있습니다. Data Virtualization in WPF.

관련 문제