문제

내 항목을 확인하고 싶습니다 ListBox UI에 올바르게 표시됩니다. 나는 이것을하는 한 가지 방법이 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)가 만들어졌습니다 - 어린이는 만들어지지 않습니다 (여기에 몇 가지 컨트롤이 추가되어야합니다. 그 중 하나는 TextBlock 항목의 텍스트가 표시됩니다).

내가 통과 한 컨트롤의 시각적 트리를 가로 지르면 PrepareItemContainer() 결과는 동일합니다. 두 경우 모두 ListBoxItem 만들어지고 그 아이들 중 어느 것도 창조되지 않습니다.

추가 할 좋은 방법을 찾을 수 없었습니다. ListBoxItem 시각적 트리에. 나는 찾았다 VirtualizingStackPanel 시각적 트리에서, 그러나 그것을 부릅니다 Children.Add() 결과 InvalidOperationException (항목에 직접 항목을 추가 할 수 없습니다 ItemPanel, 그것은 그것의 항목을 생성하기 때문에 ItemsControl). 시험처럼, 나는 그것의 전화를 시도했다 AddVisualChild() 반사를 사용하여 (보호 되었기 때문에) 작동하지 않았습니다.

도움이 되었습니까?

해결책 3

나는 이것을하는 방법을 알아 냈다고 생각합니다. 문제는 생성 된 항목이 시각적 트리에 추가되지 않았다는 것입니다. 어떤 검색 후에 내가 생각해 낼 수있는 최선은 보호 된 몇 가지 보호 방법을 호출하는 것입니다. 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);
    }
}

다른 팁

Listbox가 VirtualizingStackPanel을 사용하는 경우 빠른 모습을 보입니다.

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
      <StackPanel/>
  <ItemsPanelTemplate>
<ListBox.ItemsPanel>

당신은 이것에 대해 잘못된 길을 가고있을 수 있습니다. 내가 한 일은 내 데이터 emplate의로드 된 이벤트를 연결하는 것입니다.

<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);
}

이 접근법은 처음 표시 될 때 행의 초기화/점검을 수행하므로 모든 행을 최전하지 않습니다. 당신이 그것으로 살 수 있다면, 아마도 이것은 더 우아한 방법 일 것입니다.

Andy의 해결책은 아주 좋은 생각이지만 불완전합니다. 예를 들어, 처음 5 개의 컨테이너가 만들어지고 패널에 있습니다. 목록은 300 개 항목입니다. 이 논리로 마지막 컨테이너를 요청합니다. 그런 다음 마지막 인덱스 -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;
            }
        }
    }

이에 대해 궁금해하는 다른 사람에게는 Andy의 경우 가상화 스택 패널을 일반적인 스택 패널로 교체하는 것이 가장 좋은 솔루션이 될 것입니다.

Itemcontainergenerator에서 repayemContainer를 호출하는 이유는 작동하지 않는 경우 항목이 시각적 트리에 있어야하기 때문입니다. VirtualizingStackPanel을 사용하면 VirtualizingStackPanel이 화면에있을 것이라고 판단 할 때까지 항목은 패널의 시각적 자식으로 설정되지 않습니다.

또 다른 솔루션 (내가 사용하는 솔루션)은 자신만의 가상화 패널을 만드는 것입니다. 따라서 항목이 시각적 트리에 추가 될 때 제어 할 수 있습니다.

제 경우에는 그 부름을 발견했습니다 UpdateLayout()ItemsControl (ListBox, ListView, 등)가 시작되었습니다 ItemContainerGenerator, 발전기의 상태가 "스테워되지 않은"것에서 "생성 컨테이너"로 바뀌었고 null 컨테이너는 더 이상 반환되지 않았습니다 ItemContainerGenerator.ContainerFromItem 및/또는 ItemContainerGenerator.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);
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top