我在Windows Phone 7 Silverlight中扩展ListBox
的类有问题。我的想法是拥有一个完整的ScrollViewer
(黑色,例如填充整个手机屏幕),ItemsPresenter
(红色)有一个边距(绿色)。这用于在整个列表周围有一个边距,但滚动条从右上边缘开始,到黑暗矩形的右下边缘结束:
问题是,ScrollViewer
无法滚动到最后,它会从列表中的最后一个元素中删除50个像素。如果我使用StackPanel
代替VirtualizingStackPanel
,则边距是正确的但是列表不再虚拟化。
感谢任何想法,我已经尝试了很多,但没有任何工作。这是一个控制错误吗?
解决方案:使用MyToolkit库中InnerMargin
控件的ExtendedListBox
属性!
C#:
public class MyListBox : ListBox
{
public MyListBox()
{
DefaultStyleKey = typeof(MyListBox);
}
}
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>
更新1
我创建了一个简单的示例应用:滚动条最后无法滚动...如果您在App.xaml中将VirtualizingStackPanel
更改为StackPanel
并且它按预期工作但没有虚拟化< / p>
更新2 添加了一些示例图片。滚动条呈蓝色显示其位置。
预期结果(使用StackPanel
代替VirtualizingStackPanel
):
Correct_01:顶部的滚动条
Correct_01:中间的滚动条
Correct_01:底部的滚动条
错误的例子:
Wrong_01:保证金始终可见(例如:滚动位置中间)
唯一的解决方案是在列表末尾添加一个虚拟元素以补偿边距。我将尝试在控制逻辑中动态添加这个虚拟元素...在绑定的ObservableCollection
中添加一些逻辑,否则视图模型是没有选项的。
更新:我将最终解决方案添加为单独的答案。 查看ExtendedListBox课程。
答案 0 :(得分: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;
}
}
}
使用这个“hack”动态更改最后一个列表项的边距(不需要在绑定列表中添加内容)我开发了这个最终控件:
(列表框中有PrepareContainerForItem
的新事件,IsScrolling
的属性和事件(还有一个带LowProfileImageLoader
的扩展IsSuspended
属性,可以设置在IsScrolling
事件中,以改善滚动平滑度...)和新属性InnerMargin
以解决上述问题......
更新:查看我的MyToolkit库的ExtendedListBox类,该库提供此处描述的解决方案...
答案 1 :(得分:1)
我认为什么是更简单的方式而不是搞乱风格是 -
首先,您不需要顶部和底部边距,因为您不应该使用水平滚动条。您可以直接将这两个边距添加到列表框中。
<local:MyListBox x:Name="MainListBox" ItemsSource="{Binding Items}" Margin="0,30">
然后,要在列表框项目与滚动条之间留一点间隙(即左右边距),您只需在ItemContainerStyle
中设置左右边距为50。
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="50,0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
更新(效果影响!)
好的,请保留所有现有代码,然后将此行添加到自定义列表框样式中的ScrollViewer
。
<ScrollViewer ScrollViewer.ManipulationMode="Control">
<ItemsPresenter Margin="30,50" x:Name="itemsPresenter" />
</ScrollViewer>
似乎设置ManipulationMode="Control"
(默认设置为“系统”)修复了您的问题,但这样做可能会导致ScrollViewer
性能下降,请查看{{3} }。我认为这是一个错误。
您是否将大量数据加载到此列表框中?你真的需要在实际手机上测试性能。如果滚动顺畅,我认为这可能是一种方法,如果不是让我知道我会尝试去想别的......
答案 2 :(得分:0)
当我想在ListBox中填充时我通常会做什么,所以例如我可以让它占据整个屏幕甚至透明ApplicationBar下的部分,但仍然能够访问ListBox中的最后一项 - 我使用DataTemplateSelector(http://compositewpf.codeplex.com/SourceControl/changeset/view/52595#1024547)并为常规项定义一个(或多个)模板,并为定义特定高度的PaddingViewModel定义模板。然后 - 我确保我的ItemsSource是一个将PaddingViewModel作为最后一项的集合。然后我的填充DataTemplate在列表的末尾添加填充,我也可以使用不同模板的ListBox项。
<ListBox.ItemTemplate>
<DataTemplate>
<local:DataTemplateSelector
Content="{Binding}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<local:DataTemplateSelector.Resources>
<DataTemplate
x:Key="ItemViewModel">
<!-- Your item template here -->
</DataTemplate>
<DataTemplate
x:Key="PaddingViewModel">
<Grid
Height="{Binding Height}" />
</DataTemplate>
</local:DataTemplateSelector.Resources>
</local:DataTemplateSelector>
</DataTemplate>
</ListBox.ItemTemplate>
还有一点需要注意 - ListBox / VirtualizingStackPanel中存在一些错误,当您的项目高度不一致时 - 有时您可能看不到ListBox中的底部项目,需要向上和向下滚动以修复它。 (http://social.msdn.microsoft.com/Forums/ar/windowsphone7series/thread/58bead85-4324-411c-988f-fadb983b14a7)
答案 3 :(得分:-2)
在ItemsPresenter
(或ScrollViewer
的任何子项)上设置边距会破坏ScrollViewer
的内部逻辑。尝试在ScrollViewer
上设置与填充相同的值,即:
<ScrollViewer Padding="30,50">
...
</ScrollViewer>
更新:(查看附件后)
在ScrollViewer的模板中。 Padding属性的绑定是在控件的主网格上设置的,而不是在ScrollContentPresenter
上设置,因为它在WPF \ silverlight中完成。这使得滚动条的位置受到设置padding属性的影响。实际上,在ScrollViewer上,设置Padding相当于设置Margin。 (微软,为什么要更改最糟糕的模板!?这是故意吗?)。
无论如何,在App.xaml中列出框的样式之前添加此样式:
<Style x:Key="ScrollViewerStyle1"
TargetType="ScrollViewer">
<Setter Property="VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Padding"
Value="0" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ScrollStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="00:00:00.5" />
</VisualStateGroup.Transitions>
<VisualState x:Name="Scrolling">
<Storyboard>
<DoubleAnimation Duration="0"
To="1"
Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="VerticalScrollBar" />
<DoubleAnimation Duration="0"
To="1"
Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="HorizontalScrollBar" />
</Storyboard>
</VisualState>
<VisualState x:Name="NotScrolling" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<ScrollContentPresenter x:Name="ScrollContentPresenter"
Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" />
<ScrollBar x:Name="VerticalScrollBar"
HorizontalAlignment="Right"
Height="Auto"
IsHitTestVisible="False"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0"
Opacity="0"
Orientation="Vertical"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{TemplateBinding VerticalOffset}"
ViewportSize="{TemplateBinding ViewportHeight}"
VerticalAlignment="Stretch"
Width="5" />
<ScrollBar x:Name="HorizontalScrollBar"
HorizontalAlignment="Stretch"
Height="5"
IsHitTestVisible="False"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableWidth}"
Minimum="0"
Opacity="0"
Orientation="Horizontal"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{TemplateBinding HorizontalOffset}"
ViewportSize="{TemplateBinding ViewportWidth}"
VerticalAlignment="Bottom"
Width="Auto" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
并对列表框的样式添加一些更改:
<Setter Property="Padding" Value="30,50" />
Padding="{TemplateBinding Padding}"
和Style="{StaticResource ScrollViewerStyle1}"
。ItemsPresenter
上的保证金分配。这引入了另一个错误行为:滚动条不滚动直到屏幕底部。与削减最后一项相比,这不是一个主要问题,但仍然很烦人。