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...
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 forIsScrolling
(there is also an extendedLowProfileImageLoader
withIsSuspended
property, which can be set in theIsScrolling
event to improve scrolling smoothness...) and the new propertyInnerMargin
for the described problem...Update: Checkout the ExtendedListBox class of my MyToolkit library which provides the solution described here...
I think what would be an easier way instead of messing with styles is -
First, you don't need top and bottom margins as you shouldn't have Horizontal scrollbars anyway. You can just add these two margins to your listbox directly.
Then, to have a little gap (i.e. your left and right margin) between the listbox items and the scrollbar, you just need to set a left and right margin of 50 in your
ItemContainerStyle
.UPDATE (Performance Impact!)
Okay, please keep all your existing code, and then add this line to the
ScrollViewer
inside your custom listbox style.It seems setting
ManipulationMode="Control"
(default one is "System") has fixed your problem, however, doing this might cause a worse performance of theScrollViewer
, please take a look at this post. I think it is a bug.Do you load a lot of data into this listbox? You really need to test the performance on an actual phone. If the scrolling is smooth I think it could be a way to go, if not let me know I will try to think of something else...
Setting margins on
ItemsPresenter
(or any child of aScrollViewer
) breaks the internal logic ofScrollViewer
. Try setting the same value as Padding on theScrollViewer
, i.e.:Update: (after looking at the attached project)
In ScrollViewer's template. The binding for the Padding property was set on the main grid of the control and not on the
ScrollContentPresenter
as it's done in WPF\silverlight. This made the scroll bar's position to be affected by setting the padding property. In effect, on the ScrollViewer, setting Padding is equivalent to setting Margin. (Microsoft, why changing templates for the worst!? Was it intentional?).Anyway, add this style before the style of the list box in App.xaml:
And add some changes to the style of the list box:
<Setter Property="Padding" Value="30,50" />
Padding="{TemplateBinding Padding}"
andStyle="{StaticResource ScrollViewerStyle1}"
.ItemsPresenter
.This introduces another buggy behavior: the scroll bar is not scrolling till the bottom of the screen. Not a major issue compared to clipping the last item, but annoying nonetheless.
What I usually do when I want to have padding in a ListBox, so for example I can make it occupy the entire screen even the part under a transparent ApplicationBar, but still be able to access the last item in the ListBox - I use a DataTemplateSelector (http://compositewpf.codeplex.com/SourceControl/changeset/view/52595#1024547) and define one (or more) templates for regular items and also a template for a PaddingViewModel that defines a certain height. Then - I make sure my ItemsSource is a collection that has that PaddingViewModel as the last item. Then my padding DataTemplate adds the padding at the end of the list and I can also have ListBox items with different templates.
One more thing you have to note - there are some bugs in the ListBox/VirtualizingStackPanel that when your items are not of consistent height - you might sometimes not see the bottom items in the ListBox and need to scroll up and down to fix it. (http://social.msdn.microsoft.com/Forums/ar/windowsphone7series/thread/58bead85-4324-411c-988f-fadb983b14a7)