3

定義: WPF .NET 4.0 グリッド コントロールのデータ ソースとして機能する文字列の 2D 配列 (約 10 列、1,600 行、固定長 7 文字) を使用して、次のコード スニペットを使用してグリッドにデータを入力します。配列からの値を表示するラベル。: Grid は XAML に追加され、PopulateGrid 関数に渡されました (リスト 1 を参照)。視覚的な出力は、基本的に読み取り専用モードの表形式のデータ表現です (双方向バインディングは必要ありません)。

問題: パフォーマンスが重要な問題です。強力な Intel-i3/8GB-DDR3 PC で実行されているこの操作を完了するのに、気が遠くなるような 3...5 秒かかりました。したがって、この WPF グリッドのパフォーマンスは、通常の WinForm データ対応コントロールや Excel ワークシートなどの同様のコントロール/タスクとの比較に基づいて、予想よりも少なくとも 1 桁遅くなります。

質問 1 :上記のシナリオでWPF グリッドのパフォーマンスを向上させる方法がある場合は? 以下のリスト 1 とリスト 2 に示すコード スニペットに対する回答/改善の可能性を教えてください。

質問 1aDataGrid : 提案されたソリューションは、追加のデータベース対応コントロールへのデータ バインディングを実装できますDataTable。リスト 2 でコンバーターを追加string[,]したので、追加のコントロール(またはその他の) のプロパティを にバインドできます。したがって、最も単純な形式で、オブジェクトの WPF のデータ バインディングに関するコンパクトな (望ましくは、古いスタイルのデータ認識コントロールで行われたように、数行のコードについて) 効率的な (パフォーマンス面での) ソリューションを提供していただけますか?DataTable dtDataContextItemsSourcedt.DefaultViewDataGridDataTable

どうもありがとう。

リスト1Grid GridOut2D からWPF を設定する手順string[,] Values

#region Populate grid with 2D-array values
/// <summary>
/// Populate grid with 2D-array values
/// </summary>
/// <param name="Values">string[,]</param>
/// <param name="GridOut">Grid</param>
private void PopulateGrid(string[,] Values, Grid GridOut)
{
    try
    {
        #region clear grid, then add ColumnDefinitions/RowsDefinitions

        GridOut.Children.Clear();
        GridOut.ColumnDefinitions.Clear();
        GridOut.RowDefinitions.Clear();

        // get column num
        int _columns = Values.GetUpperBound(1) + 1;

        // add ColumnDefinitions
        for (int i = 0; i < _columns; i++)
        {
            GridOut.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        }

        // get rows num
        int _rows = Values.GetUpperBound(0) + 1;

        // add RowDefinitions
        for (int i = 0; i < _rows; i++)
        {
            GridOut.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
        }
        #endregion

        #region populate grid w/labels
        // populate grid w/labels
        for (int i = 0; i < _rows; i++)
        {
            for (int j = 0; j < _columns; j++)
            {
                // new Label control
                Label _lblValue = new Label();

                // assign value to Label
                _lblValue.Content = Values[i, j].ToString();

                // add Label to GRid
                GridOut.Children.Add(_lblValue);
                Grid.SetRow(_lblValue, i);
                Grid.SetColumn(_lblValue, j);
            }
        }
        #endregion
    }
    catch
    {
        GridOut.Children.Clear();
        GridOut.ColumnDefinitions.Clear();
        GridOut.RowDefinitions.Clear();
    }
}
#endregion

リスト 2 . 変換string[,]するDataTable

#region internal: Convert string[,] to DataTable
/// <summary>
/// Convert string[,] to DataTable
/// </summary>
/// <param name="arrString">string[,]</param>
/// <returns>DataTable</returns>
internal static DataTable Array2DataTable(string[,] arrString)
{
    DataTable _dt = new DataTable();
    try
    {
        // get column num
        int _columns = arrString.GetUpperBound(1) + 1;

        // get rows num
        int _rows = arrString.GetUpperBound(0) + 1;

        // add columns to DataTable
        for (int i = 0; i < _columns; i++)
        {
            _dt.Columns.Add(i.ToString(), typeof(string));
        }

        // add rows to DataTable
        for (int i = 0; i < _rows; i++)
        {
            DataRow _dr = _dt.NewRow();
            for (int j = 0; j < _columns; j++)
            {
                _dr[j] = arrString[i,j];
            }
            _dt.Rows.Add(_dr);
        }
        return _dt;
    }
    catch { throw; }
}
#endregion

注2。の場合のように、Content の代わりに Text プロパティを使用してLabelコントロールを置き換えることをお勧めします。これにより、実行が少し高速化されます。また、コード スニペットは VS 2012 for Win 8 と前方互換性があり、.TextBlockLabelLabel

注 3 : これまでのところ (リスト 3 の XAML を参照) へのバインドDataGridを試みましDataTableたが、パフォーマンスは非常に貧弱です (grdOutはネストGridされた であり、表形式のデータのコンテナーとして使用されました。 _dataGridは のデータ認識オブジェクト型ですDataGrid)。

リスト 3 . DataGridbinding to DataTable: パフォーマンスが悪かったので、それを削除しましたが、正常にScrollViewer動作していません。

<ScrollViewer ScrollViewer.CanContentScroll="True" VerticalScrollBarVisibility="Auto" >
    <Grid Name="grdOut">
            <DataGrid AutoGenerateColumns="True" Name="_dataGrid" ItemsSource="{Binding Path=.}" />
    </Grid>
</ScrollViewer>
4

1 に答える 1

6

Ok。すべてのコードを削除して、最初からやり直してください。

Labelsこれは、2D 文字列配列に基づく X 行数と Y 数の列を持つ「動的グリッド」に対する私の見解です。

<Window x:Class="MiscSamples.LabelsGrid"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LabelsGrid" Height="300" Width="300">
    <DockPanel>

        <Button DockPanel.Dock="Top" Content="Fill" Click="Fill"/>

        <ItemsControl ItemsSource="{Binding Items}"
                      ScrollViewer.HorizontalScrollBarVisibility="Auto"
                      ScrollViewer.VerticalScrollBarVisibility="Auto"
                      ScrollViewer.CanContentScroll="true"
                      ScrollViewer.PanningMode="Both">
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding Items}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Label Content="{Binding}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid Rows="1"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DockPanel>
</Window>

コードビハインド:

public partial class LabelsGrid : Window
{
    private LabelsGridViewModel ViewModel { get; set; }

    public LabelsGrid()
    {
        InitializeComponent();
        DataContext = ViewModel = new LabelsGridViewModel();
    }

    private void Fill(object sender, RoutedEventArgs e)
    {
        var array = new string[1600,20];

        for (int i = 0; i < 1600; i++)
        {
            for (int j = 0; j < 20; j++)
            {
                array[i, j] = "Item" + i + "-" + j;
            }
        }

        ViewModel.PopulateGrid(array);
    }
}

ビューモデル:

public class LabelsGridViewModel: PropertyChangedBase
{
    public ObservableCollection<LabelGridItem> Items { get; set; } 

    public LabelsGridViewModel()
    {
        Items = new ObservableCollection<LabelGridItem>();
    }

    public void PopulateGrid(string[,] values)
    {
        Items.Clear();

        var cols = values.GetUpperBound(1) + 1;
        int rows = values.GetUpperBound(0) + 1;

        for (int i = 0; i < rows; i++)
        {
            var item = new LabelGridItem();

            for (int j = 0; j < cols; j++)
            {
                item.Items.Add(values[i, j]);
            }

            Items.Add(item);
        }
    }
}

データ項目:

public class LabelGridItem: PropertyChangedBase
{
    public ObservableCollection<string> Items { get; set; }

    public LabelGridItem()
    {
        Items = new ObservableCollection<string>();
    }
}

PropertyChangedBase クラス (MVVM ヘルパー)

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                 {
                                                                     PropertyChangedEventHandler handler = PropertyChanged;
                                                                     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                 }));
    }
}

結果:

ここに画像の説明を入力

  • パフォーマンスは素晴らしいです。あなたが提案した10列ではなく、20列を使用していることに注意してください。ボタンをクリックすると、グリッドの塗りつぶしは即時です。UI仮想化が組み込まれているため、パフォーマンスはくだらない恐竜のwinformよりもはるかに優れていると確信しています.

  • 手続き型コードで UI 要素を作成するのとは対照的に、UI は XAML で定義されます。これは悪い習慣です。

  • UI とデータが分離されているため、保守性、スケーラビリティ、クリーン度が向上します。

  • コードをコピーして a に貼り付けてFile -> New -> WPF Application、結果を確認してください。

  • また、テキストのみを表示する場合は、非常に軽量な Text 要素であるa のTextBlock代わりに a を使用することをお勧めします。Label

  • WPF は非常に優れており、たとえエッジ ケースでパフォーマンスが低下する可能性があっても、現在存在するどの製品よりも 12837091723 優れています。

編集:

行数 (160000) に 0 を追加しました。パフォーマンスはまだ許容範囲です。グリッドにデータを入力するのに 1 秒もかかりませんでした。

私の例では、「列」が仮想化されていないことに注意してください。それらが多数ある場合、これはパフォーマンスの問題につながる可能性がありますが、それはあなたが説明したものではありません.

編集2:

あなたのコメントと説明に基づいて、今回は に基づく新しい例を作成しましたSystem.Data.DataTable。ObservableCollections も非同期のものもありません (とにかく、前の例では非同期はありませんでした)。そしてわずか10列。水平スクロールバーは、ウィンドウが小さすぎて ( Width="300")、データを表示するのに十分ではなかったために存在していました。WPF は、恐竜フレームワークとは異なり、解像度に依存せず、必要に応じてスクロールバーを表示しますが、コンテンツを使用可能なスペースに引き伸ばします (ウィンドウのサイズを変更するなどで確認できます)。

また、配列の初期化コードを Window のコンストラクターに入れているため ( の不足に対処するためINotifyPropertyChanged)、ロードして表示するのにもう少し時間がかかりますSystem.Data.DataTable

ただし、非オブジェクトにバインドするとINotifyPropertyChangedメモリ リークが発生する可能性があることに注意してください。

それでも、GridUI 仮想化を行わないため、単純なコントロールを使用することはできません。仮想化グリッドが必要な場合は、自分で実装する必要があります。

また、これに対して winforms アプローチを使用することもできません。これは、WPF では単に無関係で役に立たないものです。

    <ItemsControl ItemsSource="{Binding Rows}"
                  ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  ScrollViewer.CanContentScroll="true"
                  ScrollViewer.PanningMode="Both">
        <ItemsControl.Template>
            <ControlTemplate>
                <ScrollViewer>
                    <ItemsPresenter/>
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ItemsControl ItemsSource="{Binding ItemArray}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Rows="1"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

コードビハインド:

public partial class LabelsGrid : Window
{
    public LabelsGrid()
    {
        var array = new string[160000, 10];

        for (int i = 0; i < 160000; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                array[i, j] = "Item" + i + "-" + j;
            }
        }

        DataContext = Array2DataTable(array);
        InitializeComponent();
    }

    internal static DataTable Array2DataTable(string[,] arrString)
    {
        //... Your same exact code here
    }
}

要点は、WPF で何かを行うには、WPF の方法で行う必要があるということです。これは単なる UI フレームワークではなく、それ自体がアプリケーション フレームワークのようなものです。

編集3

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}"/>

 DataContext = Array2DataTable(array).DefaultView;

私にとっては完璧に機能します。ロード時間は 160000 行で目立ちません。使用している .Net フレームワークのバージョンは何ですか?

于 2013-05-20T23:32:31.210 に答える