문제
내 항목을 확인하고 싶습니다 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);
}