web-dev-qa-db-ja.com

ItemContainerGenerator.ContainerFromItem()はnullを返しますか?

私はうまくいかないように思える奇妙な行動を少ししています。 ListBox.ItemsSourceプロパティのアイテムを反復処理すると、コンテナーを取得できないようです。 ListBoxItemが返されることを期待していますが、nullのみを取得します。

何か案は?

私が使用しているコードは次のとおりです。

this.lstResults.ItemsSource.ForEach(t =>
    {
        ListBoxItem lbi = this.lstResults.ItemContainerGenerator.ContainerFromItem(t) as ListBoxItem;

        if (lbi != null)
        {
            this.AddToolTip(lbi);
        }
    });

ItemsSourceは現在ディクショナリに設定されており、多数のKVPが含まれています。

32
Sonny Boy

最後に問題を整理しました... VirtualizingStackPanel.IsVirtualizing="False"をXAMLに追加すると、すべてが正常に機能するようになりました。

欠点として、仮想化のパフォーマンス上の利点をすべて逃しているため、ロードルーティングを非同期に変更し、ロード中にリストボックスに「スピナー」を追加しました...

14
Sonny Boy

このStackOverflowの質問で、私のケースでうまく機能するものを見つけました。

データグリッドの行を取得

ContainerFromItemまたはContainerFromIndexを呼び出す前にUpdateLayoutおよびScrollIntoView呼び出しを配置することにより、DataGridのその部分が実現され、ContainerFromItem/ContainerFromIndexの値を返すことができます。

dataGrid.UpdateLayout();
dataGrid.ScrollIntoView(dataGrid.Items[index]);
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);

DataGridの現在の場所を変更したくない場合、これはおそらく良い解決策ではありませんが、それで問題なければ、仮想化をオフにせずに機能します。

47
Phred Menyhert
object viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
if (viewItem == null)
{
    list.UpdateLayout();
    viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
    Debug.Assert(viewItem != null, "list.ItemContainerGenerator.ContainerFromItem(item) is null, even after UpdateLayout");
}
9
epox

デバッガーを使用してコードをステップ実行し、実際に何も保持されていないか、またはas- castが間違っているかどうかを確認して、nullに変換します(通常のキャストを使用して取得できます)適切な例外)。

頻繁に発生する問題の1つは、ItemsControlがほとんどのアイテムに対して仮想化されている場合、どの時点でもコンテナーが存在しないことです。

また、アイテムコンテナを直接扱うことはお勧めしませんが、プロパティをバインドし、イベントにサブスクライブすることをお勧めします(ItemsControl.ItemContainerStyle)。

7
H.B.

私はパーティーに少し遅れていますが、ここでは私の場合に失敗しない別のソリューションがあります、

IsExpandedIsSelectedを基礎となるオブジェクトに追加し、TreeViewItemスタイルでそれらにバインドすることを提案する多くの解決策を試した後、これはほとんど機能します場合によってはまだ失敗します...

注:私の目的は、右ペインでフォルダーをクリックすると、エクスプローラーと同様に、TreeViewで選択されるミニ/カスタムエクスプローラーのようなビューを作成することでした。

private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var item = sender as ListViewItem;
    var node = item?.Content as DirectoryNode;
    if (node == null) return;

    var nodes = (IEnumerable<DirectoryNode>)TreeView.ItemsSource;
    if (nodes == null) return;

    var queue = new Stack<Node>();
    queue.Push(node);
    var parent = node.Parent;
    while (parent != null)
    {
        queue.Push(parent);
        parent = parent.Parent;
    }

    var generator = TreeView.ItemContainerGenerator;
    while (queue.Count > 0)
    {
        var dequeue = queue.Pop();
        TreeView.UpdateLayout();
        var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
        if (queue.Count > 0) treeViewItem.IsExpanded = true;
        else treeViewItem.IsSelected = true;
        generator = treeViewItem.ItemContainerGenerator;
    }
}

ここで使用される複数のトリック:

  • すべてのアイテムを上から下に展開するためのスタック
  • アイテムを見つけるために現在のレベルgeneratorを使用するようにしてください(本当に重要)
  • トップレベルのアイテムのジェネレーターがnullを決して返さないという事実

これまでのところ、非常にうまく機能しています。

  • 新しいプロパティで型を汚染する必要はありません
  • 仮想化をまったく無効にする必要はありません
4
Aybe

このサブスクリプションを使用します。

TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
  TheListBox.Dispatcher.Invoke(() =>
  {
     var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0);
     if (TheOne != null)
       // Use The One
  });
};
3
Amir

XAMLからの仮想化の無効化は機能しますが、ContainerFromItemを使用する.csファイルから仮想化を無効化する方が良いと思います

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

そのようにして、XAMLとコード間の結合を減らします。 XAMLに触れることで誰かがコードを壊すリスクを回避できます。

2
Benoit Blanchon

VirtualizingStackPanel.IsVirtualizing = "False"コントロールをファジーにします。以下の実装を参照してください。同じ問題を回避するのに役立ちます。アプリケーションを常にVirtualizingStackPanel.IsVirtualizing = "True"に設定します。

詳細情報については link をご覧ください

/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
    if (container != null)
    {
        if (container.DataContext == item)
        {
            return container as TreeViewItem;
        }

        // Expand the current container
        if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
        {
            container.SetValue(TreeViewItem.IsExpandedProperty, true);
        }

        // Try to generate the ItemsPresenter and the ItemsPanel.
        // by calling ApplyTemplate.  Note that in the 
        // virtualizing case even if the item is marked 
        // expanded we still need to do this step in order to 
        // regenerate the visuals because they may have been virtualized away.

        container.ApplyTemplate();
        ItemsPresenter itemsPresenter = 
            (ItemsPresenter)container.Template.FindName("ItemsHost", container);
        if (itemsPresenter != null)
        {
            itemsPresenter.ApplyTemplate();
        }
        else
        {
            // The Tree template has not named the ItemsPresenter, 
            // so walk the descendents and find the child.
            itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            if (itemsPresenter == null)
            {
                container.UpdateLayout();

                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            }
        }

        Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);


        // Ensure that the generator for this panel has been created.
        UIElementCollection children = itemsHostPanel.Children; 

        MyVirtualizingStackPanel virtualizingPanel = 
            itemsHostPanel as MyVirtualizingStackPanel;

        for (int i = 0, count = container.Items.Count; i < count; i++)
        {
            TreeViewItem subContainer;
            if (virtualizingPanel != null)
            {
                // Bring the item into view so 
                // that the container will be generated.
                virtualizingPanel.BringIntoView(i);

                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);
            }
            else
            {
                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);

                // Bring the item into view to maintain the 
                // same behavior as with a virtualizing panel.
                subContainer.BringIntoView();
            }

            if (subContainer != null)
            {
                // Search the next level for the object.
                TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                if (resultContainer != null)
                {
                    return resultContainer;
                }
                else
                {
                    // The object is not under this TreeViewItem
                    // so collapse it.
                    subContainer.IsExpanded = false;
                }
            }
        }
    }

    return null;
}

まだこれに問題がある場合は、最初の選択変更イベントを無視し、スレッドを使用して基本的に呼び出しを繰り返すことで、この問題を回避できました。私がやったことは次のとおりです。

private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX:Hacky workaround for an api issue
        //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason.  Basically we ignore the
        //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
        //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need I ignore the event twice just in case but I think you can get away with ignoring only the first one.
        if (_hackyfix == 0 || _hackyfix == 1)
        {
            _hackyfix++;
            Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            OnMediaSelectionChanged(sender, e);
        });
        }
        //END OF HACKY FIX//Actual code you need to run goes here}

編集10/29/2014:実際には、スレッドディスパッチャーコードさえ必要ありません。必要なものをすべてnullに設定して、最初の選択変更イベントをトリガーし、その後イベントから戻って、将来のイベントが期待どおりに機能するようにすることができます。

        private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX: Daniel note:  Very hacky workaround for an api issue
        //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason.  Basically we ignore the
        //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
        //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need
        if (_hackyfix == 0)
        {
            _hackyfix++;
            /*
            Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            OnMediaSelectionChanged(sender, e);
        });*/
            return;
        }
        //END OF HACKY FIX
        //Your selection_changed code here
        }
1
Daniel Caban

ほとんどの場合、これは仮想化関連の問題であるため、ListBoxItemコンテナーは現在表示されているアイテムに対してのみ生成されます( https://msdn.Microsoft.com/en-us/library/system.windowsを参照してください。 controls.virtualizingstackpanel(v = vs.110).aspx#Anchor_9

ListBoxを使用している場合は、代わりにListViewに切り替えることをお勧めします-ListBoxから継承し、仮想化の制御に利用できるScrollIntoView()メソッドをサポートします;

_targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
_

(上記の例では、ここで詳細に説明するDoEvents()静的メソッドも利用します; WPFさらなるコードを処理する前にバインディング更新が発生するのを待つ方法?

ListBoxコントロールとListViewコントロールの間には、他にもいくつかの小さな違いがあります( ListBoxとListViewの違いは何ですか )-ユースケースに本質的に影響しないはずです。

0
fvojvodic