Slow expanding and collapsing TreeView nodes - xaml

When a node in TreeView contains many elements, like above 2000, its expanding and collapsing is very slow. For ListView I was using incremental loading:
<ListView
Width="500"
MaxHeight="400"
IsItemClickEnabled = "False"
SelectionMode ="None"
IncrementalLoadingThreshold="5"
IncrementalLoadingTrigger="Edge"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"/>
However I do not see such option for TreeView. How this can be optimized?

Slow expanding and collapsing TreeView nodes
Both TreeView and TreeViewItem do not contain IncrementalLoading behavior, so you can't make increment loading for treeview. But you could porcess data soure with linq select-take method to implement Incremental loading function.
placing a button in the last TreeViewItem that used to load more items.
For example
<DataTemplate x:Key="FileTemplate" x:DataType="local:ExplorerItem">
<TreeViewItem AutomationProperties.Name="{x:Bind Name}" IsSelected="{x:Bind IsSelected,Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<Image Width="20" Source="../Assets/file.png" />
<TextBlock Margin="0,0,10,0" />
<TextBlock Text="{x:Bind Name}" />
<Button Background="White" Margin="15,0,0,0" x:Name="LoadMore" Visibility="{Binding IsLastItem}"
Command="{Binding LoadMoreCommand}" CommandParameter="{Binding}">
<SymbolIcon Symbol="More"/>
</Button>
</StackPanel>
</TreeViewItem>
</DataTemplate>
Code Behind
WeakReferenceMessenger.Default.Register<object>(this, (s, e) =>
{
var temp = subitems.Skip(subitems.IndexOf(e as ExplorerItem)+1).Take(20);
(e as ExplorerItem).IsLastItem = false;
foreach (var item in temp)
{
folder1.Children.Add(item);
}
folder1.Children.Last().IsLastItem = true;
});
For complete code, please refer to this link.

Related

MasterDetail ListView and editable ContentPresenter: what is wrong?

I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?
The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .

Xamarin forms xaml Listview not updating as per collection changes

Hi I am trying to update listView by adding and removing item from it. Adding Item in listview works fine in iOS and android well. But removing any item from it gives wrong out on android, it works perfect on iOS.
In Android : whenever I delete a item from listview it deletes from collection but on UI it deletes last item present on UI after deleting when I add once again it adds previously deleted item into list not new new fresh item. Please check code I used
<ListView RowHeight="100" HorizontalOptions="End" ItemsSource="{Binding GuidePrices, Mode=OneWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<controls:BindablePicker ItemsSource="{Binding Prices}" WidthRequest="150" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" />
<Button Text="Add" IsVisible="{Binding Action}">
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand CommandNameContext="{b:RelativeContext CampsiteViewPage}"
EventName="Clicked"
CommandName="AddPriceListCommand"
CommandParameter="{Binding}" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</Button>
<Button Text="Delete" IsVisible="{Binding Action, Converter={StaticResource cnvInvert}}">
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand CommandNameContext="{b:RelativeContext CampsiteViewPage}"
EventName="Clicked"
CommandName="DeletePriceListCommand"
CommandParameter="{Binding}" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</Button>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
--ViewModel code for on add and delete command
this.AddPriceListCommand = new Command((s) =>
{
if (_guidePrices != null)
{
_guidePrices.Add(new PricesListModel() { ListId = Guid.NewGuid(), Action = false, Prices = _campsite.Guides[0].Prices });
}
});
this.DeletePriceListCommand = new Command((sender) => {
var _priseList = (from priceList in _guidePrices
where priceList == (PricesListModel)sender
select priceList).ToList();
_guidePrices.RemoveAt(_guidePrices.IndexOf((PricesListModel)sender));
});
I am not able find cause why this behave on android as same code works in ios as expected.
What is the type of _guidePrices? Removing an item from a List<T> does not update the UI. You need to use the ObservableCollction<T> or raise PropertyChanged event notify UI to update

Binding an ExpanderView do a viewModel?

I made some ExpanderViews and hardcoded everything. That worked and looked nice so I wanted to clean up and only write one ExpanderView in xaml and load everything else with a binding.
As far as I understood I need a ListBox around the whole thing to make it more dynamic?
This is my code so far:
<ListBox ItemsSource="{Binding ContactDe}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding LocationName}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocationName}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Information}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ContactViewModel-Class:
public class ContactDeViewModel : INotifyPropertyChanged
{
private string _locationName;
public string LocationName
{
get
{
return _locationName;
}
set
{
if (value != _locationName)
{
_locationName = value;
NotifyPropertyChanged("LocationName");
}
}
}
private List<string> _information;
public List<string> Information
{
get
{
return _information;
}
set
{
if (value != _information)
{
_information = value;
NotifyPropertyChanged("Information");
}
}
}
}
And this is where I fill the ContactViewModel:
this.ContactDe.Add(new ContactDeViewModel()
{
LocationName = "Stuttgart",
Information = new List<string>
{
"some text"
}
}
);
this.ContactDe.Add(new ContactDeViewModel()
{
LocationName = "Böblingen",
Information = new List<string>
{
"more text"
}
}
);
I made a SampleViewModel-File where I have:
<vm:MainViewModel.ContactDe>
<vm:ContactDeViewModel LocationName="Location 1" />
<vm:ContactDeViewModel LocationName="Location 2" />
</vm:MainViewModel.ContactDe>
In the preview-window it shows me 2 ExpanderViews with Location 1 and 2. But the same code doesn't work with the emulator or a real device. I don't really understand which Binding-Acces does what. It would already help me a lot if I could see a full example. I googled many tutorials but most only show 1 side, like a xaml without seing how the data is stored.
edit:
Now I edited the viewModel, so it's not a List<string> but a List<Info> with Info only containing string Text. So now I can say ItemsSource="{Binding Text}" which should be only 1 string at a time, right?
As stated in comment to #dellywheel's answer, that you set DataContext this way :
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
that set DataContext for use in design-time only, hence it doesn't work in run-time. To set DataContext with similar approach for use in run-time, you can try this way :
<ListBox ItemsSource="{Binding ContactDe}">
<ListBox.DataContext>
<vm:MainViewModel/>
</ListBox.DataContext>
........
........
</ListBox>
or this way to set DataContext in page level :
<phone:PhoneApplicationPage>
<phone:PhoneApplicationPage.DataContext>
<vm:MainViewModel/>
</phone:PhoneApplicationPage.DataContext>
........
........
</phone:PhoneApplicationPage>
Another suggestion, prefer ObservableCollection rather than List for use along with data binding. ObservableCollection automatically notify view to refresh whenever item added to or removed from collection.
You need to change your bindings slightly
<toolkit:ExpanderView Header="{Binding LocationName}"
ItemsSource="{Binding Information}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{StaticResource honeFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
Hope that helps

VariableSizedWrapGrid and WrapGrid children size measuring

Both VariableSizedWrapGrid and WrapGrid have strange measuring - they measure all children based on the first item.
Because of that, the following XAML will clip the third item.
<VariableSizedWrapGrid Orientation="Horizontal">
<Rectangle Width="50" Height="100" Margin="5" Fill="Blue" />
<Rectangle Width="50" Height="50" Margin="5" Fill="Red" />
<Rectangle Width="50" Height="150" Margin="5" Fill="Green" />
<Rectangle Width="50" Height="50" Margin="5" Fill="Red" />
<Rectangle Width="50" Height="100" Margin="5" Fill="Red" />
</VariableSizedWrapGrid>
Seems like VariableSizedWrapGrid measures the first item and then the rest children are measured with desired size of the first one.
Any workarounds?
You need to use the Attached Properties on each Rectangle VariableSizeWrapGrid.ColumnSpan and VariableSizeWrapGrid.RowSpan as well as add an ItemHeight and ItemWidth to the VariableSizeWrapGrid:
<VariableSizedWrapGrid Orientation="Horizontal" ItemHeight="50" ItemWidth="50">
<Rectangle
VariableSizedWrapGrid.ColumnSpan="1"
VariableSizedWrapGrid.RowSpan="2"
Width="50" Height="100" Margin="5" Fill="Blue" />
</VariableSizedWrapGrid>
Its may be not the best way but this is how I have done this in my #MetroRSSReader app
<common:VariableGridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="225"
ItemHeight="{Binding ElementName=bounds, Path=Text}"
MaximumRowsOrColumns="5" Orientation="Vertical"
/>
</ItemsPanelTemplate>
</common:VariableGridView.ItemsPanel>
</common:VariableGridView>
Notice the ItemHeight value is bound to a TextBlock
<TextBlock x:Name="bounds" Grid.Row="1" Margin="316,8,0,33" Visibility="Collapsed"/>
Which is set in the LayoutAwarePage.cs
public string Fix_item_height_for_current_screen_resolution()
{
var screenheight = CoreWindow.GetForCurrentThread().Bounds.Height;
var itemHeight = screenheight < 1000 ? "100" : "140";
return itemHeight;
}
You can browse the full source code http://metrorssreader.codeplex.com/SourceControl/changeset/view/18233#265970
To use a VariableSizeWrapGrid you should create your own GridView custom control and override PrepareContainerForItemOverride and set the elements RowSpan and ColumnSpan inside that method. That way each element will have its own height/width.
Here is a nice tutorial/walk through by Jerry Nixon : http://dotnet.dzone.com/articles/windows-8-beauty-tip-using
Managed to figure this one out today. You'll need to make use of VisualTreeHelperExtension.cs in the WinRT XAML Toolkit (http://winrtxamltoolkit.codeplex.com). For me I was trying to adjust a ListView that had a GridView as its ItemsPanelTemplate, the same concept should apply for you.
1) Attach to the LayoutUpdated event of your ListView (this is when you'll want to update the sizes)
_myList.LayoutUpdated += _myList_LayoutUpdated;
2) Use VisualTreeHelperExtensions.GetDescendantsOfType() to find a common (and unique) element type in your item's data template (ex: a TextBlock that is dynamic in width):
var items = VisualTreeHelperExtensions.GetDescendantsOfType<TextBlock>(_myList);
if (items == null || items.Count() == 0)
return;
3) Get the max width of the items found:
double maxWidth = items.Max(i => i.ActualWidth) + 8;
4) Use VisualTreeHelperExtensions.GetDescendantsOfType() to find the main WrapGrid container for your ListView:
var wg = _categoryList.GetDescendantsOfType<WrapGrid>();
if (wg == null || wg.Count() != 1)
throw new InvalidOperationException("Couldn't find main ListView container");
5) Set the WrapGrid's ItemWidth to the maxWidth you calculated:
wg.First().ItemWidth = maxWidth;

Enabling ScrollViewer HorizontalSnapPoints with bindable collection

I'm trying to create a similar experience as in the ScrollViewerSample from the Windows 8 SDK samples to be able to snap to the items inside a ScrollViewer when scrolling left and right. The implementation from the sample (which works) is like this:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<StackPanel Orientation="Horizontal">
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
</ScrollViewer>
The only difference with my desired implementation is that I don't want a StackPanel with items inside, but something I can bind to. I am trying to accomplish this with an ItemsControl, but for some reason the Snap behavior does not kick in:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</ItemsControl>
</ScrollViewer>
Suggestions would be greatly appreciated!
Thanks to Denis, I ended up using the following Style on the ItemsControl and removed the ScrollViewer and inline ItemsPanelTemplate altogether:
<Style x:Key="ItemsControlStyle" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer Style="{StaticResource HorizontalScrollViewerStyle}" HorizontalSnapPointsType="Mandatory">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Getting snap points to work for bound collections can be tricky. For snap points to work immediate child of ScrollViewer should implement IScrollSnapPointsInfo interface. ItemsControl doesn't implement IScrollSnapPointsInfo and consequently you wouldn't see snapping behaviour.
To work around this issue you got couple options:
Create custom class derived from ItemsControl and implement IScrollSnapPointsInfo interface.
Create custom style for items control and set HorizontalSnapPointsType property on ScrollViewer inside the style.
I've implemented former approach and can confirm that it works, but in your case custom style could be a better choice.
Ok, here is the simplest (and standalone) example for horizontal ListView with binded items and correctly working snapping (see comments in following code).
xaml:
<ListView x:Name="YourListView"
ItemsSource="{x:Bind Path=Items}"
Loaded="YourListView_OnLoaded">
<!--Set items panel to horizontal-->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!--Some item template-->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
background code:
private void YourListView_OnLoaded(object sender, RoutedEventArgs e)
{
//get ListView
var yourList = sender as ListView;
//*** yourList style-based changes ***
//see Style here https://msdn.microsoft.com/en-us/library/windows/apps/mt299137.aspx
//** Change orientation of scrollviewer (name in the Style "ScrollViewer") **
//1. get scrollviewer (child element of yourList)
var sv = GetFirstChildDependencyObjectOfType<ScrollViewer>(yourList);
//2. enable ScrollViewer horizontal scrolling
sv.HorizontalScrollMode =ScrollMode.Auto;
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
sv.IsHorizontalRailEnabled = true;
//3. disable ScrollViewer vertical scrolling
sv.VerticalScrollMode = ScrollMode.Disabled;
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
sv.IsVerticalRailEnabled = false;
// //no we have horizontally scrolling ListView
//** Enable snapping **
sv.HorizontalSnapPointsType = SnapPointsType.MandatorySingle; //or you can use SnapPointsType.Mandatory
sv.HorizontalSnapPointsAlignment = SnapPointsAlignment.Near; //example works only for Near case, for other there should be some changes
// //no we have horizontally scrolling ListView with snapping and "scroll last item into view" bug (about bug see here http://stackoverflow.com/questions/11084493/snapping-scrollviewer-in-windows-8-metro-in-wide-screens-not-snapping-to-the-las)
//** fix "scroll last item into view" bug **
//1. Get items presenter (child element of yourList)
var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(yourList);
// or var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(sv); //also will work here
//2. Subscribe to its SizeChanged event
ip.SizeChanged += ip_SizeChanged;
//3. see the continuation in: private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
}
public static T GetFirstChildDependencyObjectOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj is T) return depObj as T;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetFirstChildDependencyObjectOfType<T>(child);
if (result != null) return result;
}
return null;
}
private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
{
//3.0 if rev size is same as new - do nothing
//here should be one more condition added by && but it is a little bit complicated and rare, so it is omitted.
//The condition is: yourList.Items.Last() must be equal to (yourList.Items.Last() used on previous call of ip_SizeChanged)
if (e.PreviousSize.Equals(e.NewSize)) return;
//3.1 get sender as our ItemsPresenter
var ip = sender as ItemsPresenter;
//3.2 get the ItemsPresenter parent to get "viewable" width of ItemsPresenter that is ActualWidth of the Scrollviewer (it is scrollviewer actually, but we need just its ActualWidth so - as FrameworkElement is used)
var sv = ip.Parent as FrameworkElement;
//3.3 get parent ListView to be able to get elements Containers
var yourList = GetParent<ListView>(ip);
//3.4 get last item ActualWidth
var lastItem = yourList.Items.Last();
var lastItemContainerObject = yourList.ContainerFromItem(lastItem);
var lastItemContainer = lastItemContainerObject as FrameworkElement;
if (lastItemContainer == null)
{
//NO lastItemContainer YET, wait for next call
return;
}
var lastItemWidth = lastItemContainer.ActualWidth;
//3.5 get margin fix value
var rightMarginFixValue = sv.ActualWidth - lastItemWidth;
//3.6. fix "scroll last item into view" bug
ip.Margin = new Thickness(ip.Margin.Left,
ip.Margin.Top,
ip.Margin.Right + rightMarginFixValue, //APPLY FIX
ip.Margin.Bottom);
}
public static T GetParent<T>(DependencyObject reference) where T : class
{
var depObj = VisualTreeHelper.GetParent(reference);
if (depObj == null) return (T)null;
while (true)
{
var depClass = depObj as T;
if (depClass != null) return depClass;
depObj = VisualTreeHelper.GetParent(depObj);
if (depObj == null) return (T)null;
}
}
About this example.
Most of checks and errors handling is omitted.
If you override ListView Style/Template, VisualTree search parts must be changed accordingly
I'd rather create inherited from ListView control with this logic, than use provided example as-is in real code.
Same code works for Vertical case (or both) with small changes.
Mentioned snapping bug - ScrollViewer bug of handling SnapPointsType.MandatorySingle and SnapPointsType.Mandatory cases. It appears for items with not-fixed sizes
.