2

過去数か月間、私は TreeView で多くのことをしてきましたが、今では UI フリーズの問題に直面しています。大量のアイテムがあり、それらのアイテムのデータ部分は非常に迅速に作成されますが、TreeViewItems の作成とそれらの視覚化 (UI スレッドで行う必要があります) には時間がかかります。

例として、シェル ブラウザと C:\Windows\System32 ディレクトリを見てみましょう。(そのためにhttp://www.codeproject.com/Articles/24237/A-Multi-Threaded-WPF-TreeView-Explorerソリューションを作り直しました。) このディレクトリには ~2500 個のファイルとフォルダーがあります。

DataItem と Visual の読み込みは別のスレッドで実装されますが、ファイルとディレクトリの情報がすばやく読み込まれるため、何のメリットもありません。TreeViewItems を作成してそれらを表示すると、アプリケーションがフリーズします。私はもう試した:

  1. アイテムをロードするときに UI スレッドに別の DispatcherPriorities を設定します。たとえば、ウィンドウは DispatcherPriority.ContextIdle でインタラクティブ (移動できました) でしたが、アイテムのロードが非常に遅くなりました..
  2. 一度に 100 個のアイテムなど、ブロック内のアイテムを作成して視覚化しますが、UI スレッドはまだフリーズしていました..

私の目標は、それらのアイテムをロードしている間、アプリケーションがインタラクティブになることです! 現時点では、これを解決する方法、ウィンドウ サイズ、スクロールバーの位置を追跡し、表示可能な項目のみをロードする独自のコントロールを実装する方法が 1 つしかありませんが、それを行うのはそれほど簡単ではなく、最終的なパフォーマンスはより良いでしょう.. :)

たくさんのビジュアル アイテムを読み込んでいるときに、アプリケーションをインタラクティブにする方法を誰かが考えているのではないでしょうか?!

コード:

完全なソリューションは、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));
    }
}
4

3 に答える 3

3

探しているものは UI 仮想化と呼ばれ、さまざまな WPF コントロールでサポートされています。特に TreeView に関して、仮想化を有効にする方法の詳細については、この記事を参照してください。

この機能を利用するには、コードから項目を直接追加するのではなく、ItemsSource プロパティを使用してコレクションから項目を提供する必要があることに注意してください。とにかくこれを行うのは良い考えですが、既存のコードで機能させるには、いくつかの再構築が必要になる場合があります。

于 2013-02-20T21:08:06.033 に答える
0

観察可能なコレクションを作成し、xamlからバインドしてみませんか?

MvvMデザインパターンを確認すると、クラスを作成し、そこにxamlをポイントして、初期化からリストを作成し、ツリービューにそのリストにバインドするように指示して、各アイテムのプロパティを表示します。リスト。

これは情報が少し少ないことは知っていますが、MvvMを実行するのは本当に簡単で、stackoverflowを調べるだけで、例が表示されます。

実際には、すべてのアイテムでbegininvokeを呼び出す必要はありません。これは、mvvmの観点からではなく、リストにバインドするだけです。

オブジェクトにインデックス付きの「レベル」を使用することもできます。

于 2013-02-20T20:56:37.323 に答える
0

この点で役立つもう 1 つの手法は、データの仮想化です。CodeProject には、WPF でのデータ仮想化について説明している優れた記事とサンプル プロジェクトがあります。

于 2013-06-14T07:46:22.640 に答える