Margin on ListBox virtualization ItemsControl not working properly

I have a problem with a class that extends ListBox in Windows Phone 7 Silverlight. The idea is to have a full ScrollViewer (black, for example, fills the entire screen of the phone) and that ItemsPresenter (red) has a margin (green). To do this, use the margin around the entire list, but the scroll bars begin in the upper right edge and end in the lower right edge of the dark rectangle:

enter image description here

The problem is that ScrollViewer cannot scroll to the very end; it cuts off 50 pixels from the last item in the list. If I use a StackPanel instead of VirtualizingStackPanel , the fields are correct, but the list is no longer virtualized.

Thanks for any ideas, I tried a lot, but nothing works. Is this a control error?

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

FROM#:

 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 application: the scrollbar cannot scroll at the end ... If you change VirtualizingStackPanel to StackPanel in App.xaml and it works as expected, but without virtualization

SampleApp.zip

Update 2 Added some sample images. The scroll bars are blue to show your position.

Expected Results (use StackPanel instead of VirtualizingStackPanel ):

Correct_01: Scroll bar at the top

enter image description here

Correct_01: Mid scroll bar

enter image description here

Correct_01: scroll bar below

enter image description here

Invalid examples:

Wrong_01: margin is always visible (example: average scroll position)

enter image description here

The only solution is to add a dummy element at the end of the list to compensate for the margin. I will try to add this dummy element dynamically inside the control logic ... Add some logic to the ObservableCollection link or the view model is not an option.

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

+8
windows-phone-7 silverlight xaml
source share
4 answers

My current solution: always change the field of the last list item ...

 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 element of the list on the fly (there is no need to add something to the linked list), I developed this final control:

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

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

+2
source share

I think it would be easier than ruining the styles -

Firstly, you do not need top and bottom margins, since in any case you should not have horizontal scrollbars. You can simply add these two fields to your list directly.

 <local:MyListBox x:Name="MainListBox" ItemsSource="{Binding Items}" Margin="0,30"> 

Then, in order to have a small space (i.e. left and right margin) between the list items and the scroll bar, you just need to set the left and right ItemContainerStyle 50 to ItemContainerStyle .

  <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> 

UPDATE (Impact on performance!)

Ok, please save all existing code, and then add this line to ScrollViewer in your custom list style.

  <ScrollViewer ScrollViewer.ManipulationMode="Control"> <ItemsPresenter Margin="30,50" x:Name="itemsPresenter" /> </ScrollViewer> 

It seems that setting ManipulationMode="Control" (the default is "System") fixed your problem, however this could lead to poor ScrollViewer performance, please see this post . I think this is a mistake.

Are you uploading a lot of data to this list? You really need to test the performance on a real phone. If the scrolling is smooth, I think this might be the way to go, if you don't let me know, I'll try to come up with something else ...

+1
source share

What I usually do when I want to have an addition in the ListBox, so for example, I can make it occupy the entire screen even the part under the transparent ApplicationBar, but still have access to the last item in the ListBox - I use DataTemplateSelector ( http: // compositewpf.codeplex.com/SourceControl/changeset/view/52595#1024547 ) and define one (or several) templates for ordinary elements, as well as a template for the PaddingViewModel that defines a specific height. Then - I'm sure my ItemsSource is a collection which has this PaddingViewModel as the last item. Then my padding DataTemplate adds a padding at the end of the list, as well as ListBox items with different templates.

 <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> 

Another thing you should note is that there are some errors in the ListBox / VirtualizingStackPanel when your items do not have a consistent height - sometimes you may not see the bottom items in the ListBox and you need to scroll up and down to fix It. ( http://social.msdn.microsoft.com/Forums/ar/windowsphone7series/thread/58bead85-4324-411c-988f-fadb983b14a7 )

0
source share

Setting fields on ItemsPresenter (or any child of a ScrollViewer ) violates the internal logic of ScrollViewer . Try setting the same value as Fill on the ScrollViewer , that is:

 <ScrollViewer Padding="30,50"> ... </ScrollViewer> 

Update: (after viewing the attached project)

In the ScrollViewer template. The binding for the Padding property was set in the main grid of the control, and not on the ScrollContentPresenter , as was done in WPF \ silverlight. This affected the position of the scrollbar by setting the padding property. Essentially, on ScrollViewer, setting Padding is equivalent to setting Margin. (Microsoft, why change the templates for the worst !? Was it intentional?).

In any case, add this style before the list style in 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> 

And add some changes to the list style:

  • Add setter for Padding to the list box: <Setter Property="Padding" Value="30,50" />
  • In the scroll viewer in the template, add Padding="{TemplateBinding Padding}" and Style="{StaticResource ScrollViewerStyle1}" .
  • Delete the field job on the ItemsPresenter .

This leads to yet another misbehavior: the scroll bar does not scroll to the bottom of the screen. Not a big problem compared to clipping the last element, but nonetheless annoying.

-2
source share

All Articles