虚拟化 ListBox 的 ItemsControl 上的边距无法正常工作 [英] Margin on ItemsControl of virtualizing ListBox not working properly
问题描述
我遇到了一个在 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
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屋!