虚拟化 ListBox 的 ItemsControl 上的边距无法正常工作 [英] Margin on ItemsControl of virtualizing ListBox not working properly

查看:19
本文介绍了虚拟化 ListBox 的 ItemsControl 上的边距无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个在 Windows Phone 7 Silverlight 中扩展 ListBox 的类的问题.这个想法是有一个完整的 ScrollViewer(黑色,例如填充整个手机屏幕)并且 ItemsPresenter(红色)有一个边距(绿色).这用于在整个列表周围留出边距,但滚动条从黑色矩形的右上边缘开始并在黑色矩形的右下边缘结束:

问题是,ScrollViewer 无法滚动到最后,它会从列表中的最后一个元素切掉 50 像素.如果我使用 StackPanel 而不是 VirtualizingStackPanel 边距是正确的,但列表不再虚拟化.

感谢您的任何想法,我尝试了很多,但没有任何效果.这是控制错误吗?

解决方案:使用 类.

解决方案

我目前的解决方案:总是改变列表最后一个元素的边距...

public 厚度 InnerMargin{得到{返回(厚度)GetValue(InnerMarginProperty);}set { SetValue(InnerMarginProperty, value);}}public static readonly DependencyProperty InnerMarginProperty =DependencyProperty.Register("InnerMargin", typeof(Thickness),typeof(ExtendedListBox), new PropertyMetadata(new Thickness(), InnerMarginChanged));私有静态无效 InnerMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var box = (ExtendedListBox)d;如果 (box.lastElement != null)box.UpdateLastItemMargin();box.UpdateInnerMargin();}私有无效 UpdateInnerMargin(){如果(滚动查看器!= null){var itemsPresenter = (ItemsPresenter)scrollViewer.Content;if (itemsPresenter != null)itemsPresenter.Margin = InnerMargin;}}私有无效 UpdateLastItemMargin(){lastElement.Margin = 新厚度(lastElementMargin.Left,lastElementMargin.Top,lastElementMargin.Right,lastElementMargin.Bottom + InnerMargin.Top + InnerMargin.Bottom);}私有 FrameworkElement lastElement = null;私人厚度 lastElementMargin;protected override void PrepareContainerForItemOverride(DependencyObject 元素,对象项){base.PrepareContainerForItemOverride(element, item);OnPrepareContainerForItem(new PrepareContainerForItemEventArgs(element, item));if ((InnerMargin.Top > 0.0 || InnerMargin.Bottom > 0.0)){if (Items.IndexOf(item) == Items.Count - 1)//是列表的最后一个元素{if (lastElement != element)//边距尚未设置{如果(最后一个元素!= null)lastElement.Margin = lastElementMargin;lastElement = (FrameworkElement)element;lastElementMargin = lastElement.Margin;UpdateLastItemMargin();}}else if (lastElement == element)//如果最后一个元素被回收,它出现在列表中 =>重置保证金{lastElement.Margin = lastElementMargin;lastElement = null;}}}

使用这个hack"来动态更改最后一个列表项的边距(无需向绑定列表添加内容)我开发了这个最终控件:

(列表框有一个 PrepareContainerForItem 的新事件,IsScrolling 的一个属性和事件(还有一个扩展的 LowProfileImageLoader>IsSuspended 属性,可以在 IsScrolling 事件中设置以提高滚动平滑度...) 和新属性 InnerMargin 用于描述的问题...

更新:检查 MyToolkit 的 ExtendedListBox 类提供此处描述的解决方案的库...

I have a problem with a class which extends ListBox in Windows Phone 7 Silverlight. The idea is to have a full ScrollViewer (black, e.g. fills the whole phone screen) and that the ItemsPresenter (red) has a margin (green). This is used to have a margin around the whole list but the scroll bars begin in the top right edge and end in the bottom right edge of the dark rectangle:

The problem is, that the ScrollViewer can't scroll to the very end, it cuts 50 pixels off of the last element in the list. If I use StackPanel instead of VirtualizingStackPanel the margins are correct BUT the list is no longer virtualizing.

Thanks for any ideas, I've tried a lot but nothing is working. Is this a control bug?

SOLUTION: Use the InnerMargin property of the ExtendedListBox control from the MyToolkit library!

C#:

public class MyListBox : ListBox
{
    public MyListBox()
    {
        DefaultStyleKey = typeof(MyListBox);
    }
}

XAML (e.g. App.xaml):

<Application 
    x:Class="MyApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone">

    <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="local:MyListBox">
                <Setter Property="ItemsPanel">
                    <Setter.Value>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel Orientation="Vertical" />
                        </ItemsPanelTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <ScrollViewer>
                                <ItemsPresenter Margin="30,50,30,50" x:Name="itemsPresenter" />
                            </ScrollViewer>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="ItemContainerStyle">
                    <Setter.Value>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate>
                                        <ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Application.Resources>

    ...
</Application> 

Update 1

I created a simple sample app: The scrollbar can't scroll at the end... If you change the VirtualizingStackPanel to StackPanel in App.xaml and it works as expected but without virtualization

SampleApp.zip

Update 2 Added some sample pictures. Scrollbars are blue to show their position.

Expected results (use StackPanel instead of VirtualizingStackPanel):

Correct_01: Scrollbar at top

Correct_01: Scrollbar at middle

Correct_01: Scrollbar at bottom

Wrong examples:

Wrong_01: Margin always visible (example: scroll position middle)

Only solution is to add a dummy element at the end of the list to compensate the margin. I'll try to add this dummy element dynamically inside the control logic... Add some logic into the bound ObservableCollection or the view model is no option.

UPDATE: I added my final solution as a separate answer. Checkout the ExtendedListBox class.

解决方案

My current solution: Always change the margin of the last element of the list...

public Thickness InnerMargin
{
    get { return (Thickness)GetValue(InnerMarginProperty); }
    set { SetValue(InnerMarginProperty, value); }
}

public static readonly DependencyProperty InnerMarginProperty =
    DependencyProperty.Register("InnerMargin", typeof(Thickness),
    typeof(ExtendedListBox), new PropertyMetadata(new Thickness(), InnerMarginChanged));

private static void InnerMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var box = (ExtendedListBox)d;
    if (box.lastElement != null)
        box.UpdateLastItemMargin();
    box.UpdateInnerMargin();
}

private void UpdateInnerMargin()
{
    if (scrollViewer != null)
    {
        var itemsPresenter = (ItemsPresenter)scrollViewer.Content;
        if (itemsPresenter != null)
            itemsPresenter.Margin = InnerMargin;
    }
}

private void UpdateLastItemMargin()
{
    lastElement.Margin = new Thickness(lastElementMargin.Left, lastElementMargin.Top, lastElementMargin.Right,
        lastElementMargin.Bottom + InnerMargin.Top + InnerMargin.Bottom);
}

private FrameworkElement lastElement = null;
private Thickness lastElementMargin;
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
    base.PrepareContainerForItemOverride(element, item);
    OnPrepareContainerForItem(new PrepareContainerForItemEventArgs(element, item));

    if ((InnerMargin.Top > 0.0 || InnerMargin.Bottom > 0.0))
    {
        if (Items.IndexOf(item) == Items.Count - 1) // is last element of list
        {
            if (lastElement != element) // margin not already set
            {
                if (lastElement != null)
                    lastElement.Margin = lastElementMargin;
                lastElement = (FrameworkElement)element;
                lastElementMargin = lastElement.Margin;
                UpdateLastItemMargin();
            }
        }
        else if (lastElement == element) // if last element is recycled it appears inside the list => reset margin
        {
            lastElement.Margin = lastElementMargin;
            lastElement = null; 
        }
    }
}

Using this "hack" to change the margin of the last list item on the fly (no need to add something to the bound list) I developed this final control:

(The listbox has a new event for PrepareContainerForItem, a property and event for IsScrolling (there is also an extended LowProfileImageLoader with IsSuspended property, which can be set in the IsScrolling event to improve scrolling smoothness...) and the new property InnerMargin for the described problem...

Update: Checkout the ExtendedListBox class of my MyToolkit library which provides the solution described here...

这篇关于虚拟化 ListBox 的 ItemsControl 上的边距无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆