سؤال

وأريد للتحقق من أن يتم عرض العناصر في بلدي ListBox بشكل صحيح في واجهة المستخدم. أنا أحسب طريقة واحدة لتحقيق ذلك هي من خلال الذهاب الى كل من أبناء ListBox في شجرة البصرية، والحصول على نصوصها، ثم قارن ذلك مع ما أتوقع النص ليكون.

والمشكلة مع هذا النهج هو أن ListBox داخليا يستخدم 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 يحتوي على 20 بندا، مع 12 العناصر الأولى واضحة في البداية. النص للعناصر 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);
    }
}

نصائح أخرى

ومجرد النظر سريعة، إذا كان مربع القائمة يستخدم VirtualizingStackPanel - ربما سيكون كافيا لاستبدالها StackPanel مثل

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

ويمكنك أن تسير على هذا الطريق الخاطئ. ما فعلته هو ربط الحدث المحملة من [محتوى] بلدي DataTemplate:

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

وهذا النهج لا التهيئة / التحقق من الصف عند عرضه لأول مرة، لذلك لن تفعل كل الصفوف مقدما. إذا كنت تستطيع العيش مع ذلك، ثم ربما هذا هو الأسلوب أكثر أناقة.

والحل من أندي هو فكرة جيدة جدا، ولكن غير كامل. على سبيل المثال، يتم إنشاء 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;
            }
        }
    }

لأي شخص آخر يتساءل عن هذا، في حال اندي، وربما مبادلة خارج VirtualizingStackPanel مع StackPanel العادي سيكون الحل الأفضل هنا.

والسبب تدعو PrepareItemContainer على ItemContainerGenerator لا يعمل هو أن يجب أن يكون عنصر في شجرة البصرية لPrepareItemContainer للعمل. مع VirtualizingStackPanel، لن يتم تعيين البند كطفل البصري للوحة حتى يحدد VirtualizingStackPanel أنه / على وشك أن يكون على الشاشة.

وهناك حل آخر (واحد يمكنني استخدام) هو خلق VirtualizingPanel الخاص بك، حتى تتمكن من السيطرة عند إضافة عناصر إلى شجرة البصرية.

في حالتي، وجدت أن يدعو UpdateLayout() على ItemsControl (ListBox، ListView، الخ) التي تصل ItemContainerGenerator لها، مثل هذا الوضع المولد تغيرت من "NotStarted" إلى "GeneratingContainers"، وكانت حاويات 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