关于虚拟化ListBox的ItemsControl的保证金不能正常工作

Ric*_*ter 9 silverlight xaml windows-phone-7 windows-phone-7.1

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

在此输入图像描述

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

感谢任何想法,我已经尝试了很多,但没有任何工作.这是一个控制错误吗?

解决方案:使用MyToolkit库中控件的InnerMargin属性!ExtendedListBox

C#:

public class MyListBox : ListBox
{
    public MyListBox()
    {
        DefaultStyleKey = typeof(MyListBox);
    }
}
Run Code Online (Sandbox Code Playgroud)

XAML(例如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> 
Run Code Online (Sandbox Code Playgroud)

更新1

我创建了一个简单的示例应用程序:滚动条最后无法滚动...如果您在App.xaml中更改为VirtualizingStackPanelto StackPanel并且它按预期工作但没有虚拟化

SampleApp.zip

更新2 添加了一些示例图片.滚动条呈蓝色显示其位置.

预期结果(使用StackPanel而不是VirtualizingStackPanel):

Correct_01:顶部的滚动条

在此输入图像描述

Correct_01:中间的滚动条

在此输入图像描述

Correct_01:底部的滚动条

在此输入图像描述

错误的例子:

Wrong_01:边距始终可见(例如:滚动位置中间)

在此输入图像描述

唯一的解决方案是在列表的末尾添加一个虚拟元素来补偿边距.我将尝试在控制逻辑中动态添加此虚拟元素...将一些逻辑添加到绑定中ObservableCollection或视图模型中没有选项.

更新:我将我的最终解决方案添加为单独的答案.签出ExtendedListBox类.

Ric*_*ter 2

我当前的解决方案:始终更改列表最后一个元素的边距......

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; 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

更新:查看我的 MyToolkit 库的ExtendedListBox类,它提供了此处描述的解决方案...