6

私の項目がListBoxUI で正しく表示されることを確認したいと思います。これを行う 1 つの方法はListBox、ビジュアル ツリー内の のすべての子を調べてテキストを取得し、それを期待するテキストと比較することだと考えました。

このアプローチの問題点は、内部的ListBoxに a を使用しVirtualizingStackPanelてアイテムを表示するため、表示されているアイテムのみが作成されることです。私は最終的にクラスに出くわしましたItemContainerGenerator。これは、指定された項目のビジュアル ツリーにコントロールを作成するように WPF に強制するように見えます。残念ながら、それは私にいくつかの奇妙な副作用を引き起こしています. のすべてのアイテムを生成するコードは次のListBoxとおりです。

List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
    bool isNewlyRealized;
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
    {
        isNewlyRealized = false;
        DependencyObject cntr = generator.GenerateNext(out isNewlyRealized);
        if(isNewlyRealized)
        {
            generator.PrepareItemContainer(cntr);
        }

        string itemText = GetControlText(cntr);
        generatedItems.Add(itemText);
    }
}

(必要に応じて のコードを提供できますがGetItemText()、 が見つかるまでビジュアル ツリーをトラバースするだけTextBlockです。アイテムにテキストを含める方法は他にもあると思いますが、アイテムを取得したら修正します。世代は正常に動作しています。)

私のアプリでItemsListBoxは、最初に表示される最初の 12 項目で、20 項目が含まれています。最初の 14 項目のテキストは正しいです (それらのコントロールが既に生成されている可能性があります)。ただし、項目 15 ~ 20 については、テキストがまったく表示されません。さらに、一番下までスクロールすると、ItemsListBox項目 15 ~ 20 のテキストも空白になります。したがって、何らかの方法でコントロールを生成するための WPF の通常のメカニズムに干渉しているようです。

私は何を間違っていますか?ItemsControlアイテムを強制的にビジュアル ツリーに追加する別の/より良い方法はありますか?

更新:修正方法はわかりませんが、これが発生する理由はわかったと思います。PrepareItemContainer()への呼び出しにより、アイテムを表示するために必要なコントロールが生成され、コンテナーがビジュアル ツリーの正しい場所に追加されると仮定しました。これらのことのいずれも行っていないことが判明しました。コンテナは、下にスクロールして表示するまで追加されません。ItemsControlその時点では、コンテナ自体(つまりListBoxItem)のみが作成されます-その子は作成されません(ここにいくつかのコントロールが追加され、そのうちの1つがアイテムのTextBlockテキストを表示します)。

結果に渡したコントロールのビジュアル ツリーをトラバースするとPrepareItemContainer()、結果は同じになります。どちらの場合も、 のみListBoxItemが作成され、その子は作成されません。

ListBoxItemをビジュアル ツリーに追加する良い方法が見つかりませんでした。ビジュアル ツリーでを見つけましたVirtualizingStackPanelが、そのChildren.Add()結果を で呼び出します ( の項目を生成するため、InvalidOperationException項目を に直接追加することはできません)。テストとして、リフレクションを使用して呼び出してみましたが(保護されているため)、それも機能しませんでした。ItemPanelItemsControlAddVisualChild()

4

6 に答える 6

3

ListBox が VirtualizingStackPanel を使用している場合、簡単に見てみると、おそらく StackPanel のように置き換えるだけで十分でしょう。

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
      <StackPanel/>
  <ItemsPanelTemplate>
<ListBox.ItemsPanel>
于 2009-03-10T13:27:40.590 に答える
3

あなたはこれについて間違った方法で進んでいるかもしれません。私がしたことは、私の DataTemplate [のコンテンツ] の Loaded イベントをフックすることです。

<DataTemplate DataType="{x:Type local:ProjectPersona}">
  <Grid Loaded="Row_Loaded">
    <!-- ... -->
  </Grid>
</DataTemplate>

...そして、イベント ハンドラーで新しく表示された行を処理します。

private void Row_Loaded(object sender, RoutedEventArgs e)
{
    Grid grid = (Grid)sender;
    Carousel c = (Carousel)grid.FindName("carousel");
    ProjectPersona project = (ProjectPersona)grid.DataContext;
    if (project.SelectedTime != null)
        c.ScrollItemIntoView(project.SelectedTime);
}

このアプローチは、行が最初に表示されるときに行の初期化/チェックを行うため、事前にすべての行を行うわけではありません。あなたがそれを受け入れることができるなら、おそらくこれはよりエレガントな方法です.

于 2010-01-20T12:23:25.380 に答える
1

私はこれを行う方法を理解したと思います。問題は、生成されたアイテムがビジュアルツリーに追加されなかったことです。いくつか検索した後、私が思いつくことができる最善の方法は、の保護されたメソッドを呼び出すことVirtualizingStackPanelですListBox。これは理想的ではありませんが、テスト専用なので、私はそれと一緒に暮らす必要があると思います。

これは私のために働いたものです:

VirtualizingStackPanel itemsPanel = null;
FrameworkElementFactory factory = control.ItemsPanel.VisualTree;
if(null != factory)
{
    // This method traverses the visual tree, searching for a control of
    // the specified type and name.
    itemsPanel = FindNamedDescendantOfType(control,
        factory.Type, null) as VirtualizingStackPanel;
}

List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
    bool isNewlyRealized;
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
    {
        isNewlyRealized = false;
        UIElement cntr = generator.GenerateNext(out isNewlyRealized) as UIElement;
        if(isNewlyRealized)
        {
            if(i >= itemsPanel.Children.Count)
            {
                itemsPanel.GetType().InvokeMember("AddInternalChild",
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
                    Type.DefaultBinder, itemsPanel,
                    new object[] { cntr });
            }
            else
            {
                itemsPanel.GetType().InvokeMember("InsertInternalChild",
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
                    Type.DefaultBinder, itemsPanel,
                    new object[] { i, cntr });
            }

            generator.PrepareItemContainer(cntr);
        }

        string itemText = GetControlText(cntr);
        generatedItems.Add(itemText);
    }
}
于 2009-03-13T17:24:01.437 に答える
1

アンディのソリューションは非常に良いアイデアですが、不完全です。たとえば、最初の 5 つのコンテナが作成され、パネルに表示されます。リストには 300 個以上のアイテムがあります。このロジック ADD を使用して、最後のコンテナーを要求します。次に、この logis ADD を使用して、最後のインデックス - 1 コンテナーを要求します。それが問題です。パネル内の子の順序が無効です。

これに対する解決策:

    private FrameworkElement GetContainerForIndex(int index)
    {
        if (ItemsControl == null)
        {
            return null;
        }

        var container = ItemsControl.ItemContainerGenerator.ContainerFromIndex(index -1);
        if (container != null && container != DependencyProperty.UnsetValue)
        {
            return container as FrameworkElement;
        }
        else
        {

            var virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);
            if (virtualizingPanel == null)
            {
                // do something to load the (perhaps currently unloaded panel) once
            }
            virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);

            IItemContainerGenerator generator = ItemsControl.ItemContainerGenerator;
            using (generator.StartAt(generator.GeneratorPositionFromIndex(index), GeneratorDirection.Forward))
            {
                bool isNewlyRealized = false;
                container = generator.GenerateNext(out isNewlyRealized);
                if (isNewlyRealized)
                {
                    generator.PrepareItemContainer(container);
                    bool insert = false;
                    int pos = 0;
                    for (pos = virtualizingPanel.Children.Count - 1; pos >= 0; pos--)
                    {
                        var idx = ItemsControl.ItemContainerGenerator.IndexFromContainer(virtualizingPanel.Children[pos]);
                        if (!insert && idx < index)
                        {
                            ////Add
                            virtualizingPanel.GetType().InvokeMember("AddInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { container });
                            break;
                        }
                        else
                        {
                            insert = true;
                            if (insert && idx < index)
                            {
                                break;
                            }
                        }
                    }

                    if (insert)
                    {
                        virtualizingPanel.GetType().InvokeMember("InsertInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { pos + 1, container });
                    }
                }

                return container as FrameworkElement;
            }
        }
    }
于 2013-12-03T20:24:25.137 に答える
0

これについて疑問に思っている人にとっては、Andy の場合、おそらく VirtualizingStackPanel を通常の StackPanel と交換することが最善の解決策になるでしょう。

ItemContainerGenerator での PrepareItemContainer の呼び出しが機能しない理由は、PrepareItemContainer が機能するためにアイテムがビジュアル ツリーに存在する必要があるためです。VirtualizingStackPanel を使用すると、VirtualizingStackPanel が画面上にある、または画面上に表示されようとしていると判断するまで、項目はパネルの視覚的な子として設定されません。

別の解決策 (私が使用するもの) は、独自の VirtualizingPanel を作成することです。これにより、ビジュアル ツリーにアイテムが追加されるタイミングを制御できます。

于 2011-06-08T16:15:48.223 に答える
-1

私の場合、 ( 、 など) を呼び出すとUpdateLayout()ItemsControl起動ListBoxListViewItemContainerGeneratorジェネレーターのステータスが "NotStarted" から "GeneratingContainers" に変わり、コンテナーがand/ornullによって返されなくなったことがわかりました。ItemContainerGenerator.ContainerFromItemItemContainerGenerator.ContainerFromIndex

例えば:

    public static bool FocusSelectedItem(this ListBox listbox)
    {
        int ix;
        if ((ix = listbox.SelectedIndex) < 0)
            return false;

        var icg = listbox.ItemContainerGenerator;
        if (icg.Status == GeneratorStatus.NotStarted)
            listbox.UpdateLayout();

        var el = (UIElement)icg.ContainerFromIndex(ix);
        if (el == null)
            return false;

        listbox.ScrollIntoView(el);

        return el == Keyboard.Focus(el);
    }
于 2015-05-14T23:05:26.770 に答える