2009-03-10 21 views
6

Tôi muốn xác minh rằng các mục trong số ListBox của tôi được hiển thị chính xác trong giao diện người dùng. Tôi đã tìm ra một cách để làm điều này là đi qua tất cả các con của số ListBox trong cây thị giác, lấy văn bản của chúng, và sau đó so sánh nó với những gì tôi mong đợi văn bản được.Buộc WPF tạo các mục trong một ItemsControl

Vấn đề với cách tiếp cận này là nội bộ ListBox sử dụng VirtualizingStackPanel để hiển thị các mục của nó, vì vậy chỉ các mục hiển thị mới được tạo. Cuối cùng tôi đã đi qua lớp ItemContainerGenerator, có vẻ như nó sẽ buộc WPF tạo các điều khiển trong cây trực quan cho mục được chỉ định. Thật không may, đó là gây ra một số tác dụng phụ lạ đối với tôi. Đây là mã của tôi để tạo ra tất cả các mục trong 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); 
    } 
} 

(tôi có thể cung cấp mã cho GetItemText() nếu bạn muốn, nhưng nó chỉ đi qua cây thị giác cho đến khi một TextBlock được tìm thấy tôi nhận ra đó là những cách khác để có văn bản trong một mục, nhưng tôi sẽ sửa chữa điều đó một khi tôi nhận được thế hệ mục hoạt động đúng.)

Trong ứng dụng của tôi, ItemsListBox chứa 20 mục, với 12 mục đầu tiên nhìn thấy được. Văn bản cho 14 mục đầu tiên là chính xác (có thể do các điều khiển của chúng đã được tạo). Tuy nhiên, đối với các hạng mục 15-20, tôi không nhận được bất kỳ văn bản nào cả. Ngoài ra, nếu tôi cuộn xuống cuối của ItemsListBox, văn bản của các mục 15-20 cũng trống. Vì vậy, có vẻ như tôi đang can thiệp vào cơ chế bình thường của WPF để tạo ra một số điều khiển như thế nào.

Tôi đang làm gì sai? Có cách nào khác tốt hơn để buộc các mục trong một số ItemsControl được thêm vào cây trực quan không?

Cập nhật: Tôi nghĩ rằng tôi đã tìm thấy lý do tại sao điều này xảy ra, mặc dù tôi không biết cách khắc phục. Giả định rằng cuộc gọi đến PrepareItemContainer() sẽ tạo bất kỳ điều khiển cần thiết nào để hiển thị mục và sau đó thêm vùng chứa vào cây trực quan ở vị trí chính xác. Nó chỉ ra rằng nó không phải là làm một trong những điều này. Vùng chứa không được thêm vào ItemsControl cho đến khi tôi cuộn xuống để xem nó và tại thời điểm đó chỉ có chính vùng chứa (tức là ListBoxItem) được tạo ra - con của nó không được tạo (có một vài điều khiển được thêm vào đây, một trong số đó phải là TextBlock sẽ hiển thị văn bản của mục).

Nếu tôi duyệt cây thị giác của điều khiển mà tôi đã chuyển đến PrepareItemContainer() kết quả giống nhau. Trong cả hai trường hợp, chỉ có ListBoxItem được tạo và không có con nào được tạo.

Tôi không thể tìm thấy cách tốt để thêm ListBoxItem vào cây trực quan. Tôi tìm thấy số VirtualizingStackPanel trong cây trực quan, nhưng gọi số Children.Add() kết quả là InvalidOperationException (không thể thêm các mục trực tiếp vào ItemPanel vì nó tạo mục cho số ItemsControl). Cũng giống như một bài kiểm tra, tôi đã thử gọi số AddVisualChild() bằng cách sử dụng Reflection (vì nó được bảo vệ), nhưng điều đó cũng không hoạt động. tìm

+0

Tôi không hiểu. Tại sao bạn muốn làm điều này? Thử nghiệm? –

+0

Có - chỉ dành cho mục đích thử nghiệm. – Andy

+0

Bạn có thể đặt ['VirtualizingStackPanel.IsVirtualizing'] (http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.isvirtualizing.aspx) đính kèm thuộc tính thành' false' trên 'ItemsListBox 'trước khi thêm các mục. Khi bạn đặt nó thành 'true' một lần nữa mọi thứ sẽ bắt đầu ảo hóa khi bạn cuộn. –

Trả lời

1

Tôi nghĩ rằng tôi đã tìm ra cách để làm điều này. Vấn đề là các mục đã tạo không được thêm vào cây trực quan.Sau khi tìm kiếm một số, tốt nhất tôi có thể đưa ra là gọi một số phương pháp được bảo vệ của VirtualizingStackPanel trong ListBox. Trong khi điều này không lý tưởng, vì nó chỉ để thử nghiệm, tôi nghĩ rằng tôi sẽ phải sống với nó.

Đây là những gì làm việc cho tôi:

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

Chỉ cần nhanh chóng, nếu ListBox sử dụng VirtualizingStackPanel - có thể nó sẽ đủ để thay thế nó với StackPanel như

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

Trong khi điều đó không sửa chữa nó, tôi không muốn thay đổi ListBox chính nó nếu tôi có thể tránh nó, vì nó sẽ là tốt hơn để sử dụng một VirtualizingStackPanel trong ứng dụng thực tế. – Andy

+0

Tôi đã thử điều này với một Combobox và vì một lý do nào đó nó không hoạt động. –

3

Bạn có thể đi về việc này một cách sai lầm. Những gì tôi đã làm là treo lên các sự kiện Loaded của [nội dung của] DataTemplate của tôi:

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

... và sau đó xử lý hàng mới hiển thị trong xử lý sự kiện:

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

Cách tiếp cận này không khởi tạo/kiểm tra hàng khi nó được hiển thị lần đầu tiên, vì vậy nó sẽ không làm tất cả các hàng ở phía trước. Nếu bạn có thể sống với điều đó, thì có lẽ đây là phương pháp thanh lịch hơn.

0

Đối với bất kỳ ai khác tự hỏi về điều này, trong trường hợp của Andy, có lẽ hoán đổi VirtualizingStackPanel với một StackPanel bình thường sẽ là giải pháp tốt nhất ở đây.

Lý do gọi PrepareItemContainer trên ItemContainerGenerator không hoạt động là một mục phải nằm trong cây trực quan để PrepareItemContainer hoạt động. Với VirtualizingStackPanel, mục sẽ không được thiết lập như là một đứa trẻ trực quan của bảng điều khiển cho đến khi VirtualizingStackPanel xác định rằng nó đang/sắp được hiển thị trên màn hình.

Một giải pháp khác (giải pháp tôi sử dụng) là tạo VirtualizingPanel của riêng bạn, vì vậy bạn có thể kiểm soát thời điểm các mục được thêm vào cây trực quan.

1

Giải pháp từ Andy là một ý tưởng rất hay nhưng chưa hoàn chỉnh. Ví dụ: 5 vùng chứa đầu tiên được tạo và trong bảng điều khiển. Danh sách này có 300 mục. Tôi yêu cầu container cuối cùng, với logic này, ADD. Sau đó, tôi yêu cầu chỉ mục cuối cùng - 1 container, với ADD này logis! Đó chính là vấn đề. Thứ tự của trẻ em bên trong bảng điều khiển là không hợp lệ.

Một giải pháp cho việc này:

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

Trong trường hợp của tôi, tôi thấy rằng gọi UpdateLayout() trên ItemsControl (ListBox, ListView, vv) bắt đầu lên ItemContainerGenerator của nó, như vậy mà tình trạng của máy phát điện thay đổi từ "NotStarted "đến" GeneratingContainers "và null vùng chứa không còn được trả lại bởi ItemContainerGenerator.ContainerFromItem và/hoặc ItemContainerGenerator.ContainerFromIndex.

Ví dụ:

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); 
    } 
Các vấn đề liên quan