Let ListView scroll to selected item - xaml

I have a WinRT/C#/XAML app with a view that has a vertical ListView of items. Depending on the amount of items the ListView shows a vertical scrollbar. Here's the XAML definition:
<UserControl.Resources>
<CollectionViewSource
x:Name="myViewSource"
Source="{Binding myViewModel.Items}" />
</UserControl.Resources>
...
<ListView
x:Name="myListView"
ItemsSource="{Binding Source={StaticResource myViewSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ListView>
Now everytime I navigate to this view, the selected item of the ListView is chosen by setting the databound SelectedItem property in the view model from code behind (OnNavigatedTo). My problem: the ListView doesn't scroll automatically to this selected item. The scrollbar remains at the top of the ListView and the user has to scroll manually to see the selected item.
I tried to execute myListView.ScrollIntoView(MyViewModel.SelectedItem); after setting the SelectedItem in the code behind (in OnNavigatedTo), but it doesn't work. The scrollbar remains at the top.
I'm aware of this thread on SO: Scroll WinRT ListView to particular group .
This seems to be a similar problem. But when I walk the visual tree of the ListView manually or with the WinRT XAML Toolkit, it doesn't find a ScrollViewer (returns null instead).

Thanks to Filip I noticed that calling ScrollIntoView() in OnNavigatedTo() was too early, because the ListView control is not loaded yet in this place.
The first solution idea was to bind the Loaded event of the ListView:
myListView.Loaded += (s, e) =>
myListView.ScrollIntoView(MyViewModel.SelectedItem);
Unfortunately that causes a nasty visual effect, where current ListView items overlap with the selected item for parts of a second, before everything is rearranged well.
The final solution I found is to call ScrollIntoView() asynchronously via the Dispatcher of the view:
myListView.Loaded += (s, e) => Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => myListView.ScrollIntoView(MyViewModel.SelectedItem));
With this solution the layouting works fine.

I had a similar need and resolved it in a slightly different manner. I subscribed to the SelectionChangedEvent from the ListView and performed the scrolling within the handler.
XAML:
<ListView x:Name="myListView" SelectionChanged="myListView_SelectionChanged" ...>
</ListView>
Code:
private void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myListView.ScrollIntoView(myListView.SelectedItem);
}

Related

What is the difference between null and transparent brush in the Background or Fill

For example we have a Border. What the difference beetween these XAMLs?
1) Background="Transparent"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="Transparent"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
2) Background="{x:Null}"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="{x:Null}"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
Both of these borders looks identical. But what the difference?
The difference is if we set null background the Border will not support hit-testing, that's why routed events like PonterPressed will not be raised.
Conversely though, if we set Transparent background events will be raised.
To illustrate this let's write code-behind.
using Windows.UI.Xaml.Media;
namespace App1 {
public sealed partial class MainPage : Page {
public MainPage() {
this.InitializeComponent();
}
void Border_PointerPressed(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Red);
}
void Border_PointerReleased(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Transparent);
}
}
}
1) Let's use the first XAML, compile our app and run it. Try to tap inside the square. The square becomes red because the events are rised and the handlers calls.
2) Now let's use the second XAML, compile the app, run it, tap inside the square. Nothing happens because the events are not rised. The handlers are not calls.
For completeness, I found this link http://msdn.microsoft.com/en-us/library/hh758286.aspx#hit_testing explaining this rather well - see especially the second bullet point:
Hit testing and input events
Determining whether and where in UI an element is visible to mouse,
touch, and stylus input is called hit testing. For touch actions and
also for interaction-specific or manipulation events that are
consequences of a touch action, an element must be hit-test visible in
order to be the event source and fire the event that is associated
with the action. Otherwise, the action passes through the element to
any underlying elements or parent elements in the visual tree that
could interact with that input. There are several factors that affect
hit testing, but you can determine whether a given element can fire
input events by checking its IsHitTestVisible property. This property
returns true only if the element meets these criteria:
The element's Visibility property value is Visible.
The element's Background or Fill property value is not null. A null Brush value results in transparency and hit test invisibility. (To
make an element transparent but also hit testable, use a Transparent
brush instead of null.) Note Background and Fill aren't defined by
UIElement, and are instead defined by different derived classes such
as Control and Shape. But the implications of brushes you use for
foreground and background properties are the same for hit testing and
input events, no matter which subclass implements the properties.
If the element is a control, its IsEnabled property value must be true.
The element must have actual dimensions in layout. An element where either ActualHeight and ActualWidth are 0 won't fire input events.

Start An Event After Scrolling

I'm new to Windows Phone apps development, and I've created a scrolling menu using the following xaml code :
<ScrollViewer HorizontalAlignment="Left" Margin="18,0,0,0" Name="scrollViewer1" VerticalAlignment="Top" Width="450" HorizontalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel Height="Auto" Name="stackPanel1" Width="Auto">
<Button Height="620" FontSize="120" Name="gotoGmail" Width="Auto">Gmail</Button>
<Button Height="620" FontSize="120" Name="gotoYahoo" Width="Auto">Yahoo</Button>
</StackPanel>
</ScrollViewer>
I'd like to know whether it's possible to start an event once the user scrolls the menu from one button to another. If it is possible, i'd be grateful if you could explain how. If it's not , i'd like to hear about how could I do it using different tools rather than ScrollViewer. Thanks in advance !
There's no "Scrolled" event on the ScrollViewer, but what you can do is two-way bind a property to VerticalOffset. That lets you trigger an event/command from your view/viewmodel when the scroll changes.
Something like this:
<ScrollViewer VerticalOffset="{Binding VerticalOffset,Mode=TwoWay}" ...
And then in the data context:
public double VerticalOffset
{
get { return _verticalOffset; }
set
{
_verticalOffset = value;
// call "on scroll changed" actions here
}
}
private double _verticalOffset = 0;
how could I do it using different tools rather than ScrollViewer
You can of course make a scrolling menu using other approaches. I'll bet there is some nifty approach you could figure, using the WinRT transitions/animations stuff, but I'm not familiar enough with those to say. Here are some others (not sure which would be best/easiest for your scenario):
Probably using Canvas would be a quick-and-dirty way to do it (just set up buttons that set off Canvas.Left and Canvas.Top animations).
Extending ItemsControl along with a custom ControlTemplate would be a good approach if you want to create a re-usable component.
I like extending Panel, but you have to do the animations manually using a DispatcherTimer, and you have to lay out the buttons yourself using Measure and Arrange.

Keep element in view while scrolling

Simpel question, I have a windows phone page that contains a scrollviewer with inside it an image, a textblock and a richtextbox.
Now when the user starts scrolling I want to keep the textblock in view on top when the image has scrolled outside the page.
So the effect is, user starts scrolling upwards, everything scrolls upwards, when the image is outside the page, the textblock stays at the top of the page but the richtextbox keeps scrolling upwards.
Any thoughts?
Here is a way to reach this result:
First, the layout. I've set a grid, with two rows. The first is empty, and will host the header when we need to freeze it. The second row contains the scrollviewer.
Inside the scrollviewer, I've put the controls in a grid, but you can use whatever container suits you.
<ScrollViewer Grid.Row="1"
Margin="0"
Padding="0"
x:Name="ParentScroll"
ManipulationMode="Control"
MouseMove="ParentScroll_MouseMove">
<Grid x:Name="ChildGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Image Source="Picture.jpg" Grid.Row="0"/>
<TextBlock Text="Header" Grid.Row="1" x:Name="TextHeader" />
<RichTextBox Grid.Row="2" x:Name="RichText">
<Paragraph>
<Bold>RichTextBox</Bold>
<!-- More stuff -->
</Paragraph>
</RichTextBox>
</Grid>
</ScrollViewer>
I use the MouseMove event to be notified of the scrolling event. You can also dig into the template, extract the ScrollBar control, and subscribe to the ValueChanged event, as described here: http://social.msdn.microsoft.com/Forums/wpapps/en-US/81fcd34e-6ec9-48d0-891e-c53a53344553/scrollviewer-synchronization
Note that you need to set ManipulationMode to Control or the position of the controls won't be updated at a smooth rate. I guess it's due to some internal optimization.
In the code behind, I use the TransformToVisual method to compute the relative position of the controls to the ScrollViewer. This way, I can know when the header goes out of view. When it does, I remove it from the child grid, and put it outside of the ScrollViewer, in the parent grid. When the top of the RichTextBox goes out of view, I put the header back into the ScrollViewer:
private void ParentScroll_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (Grid.GetRow(this.TextHeader) == 1)
{
var generalTransform = TextHeader.TransformToVisual(ParentScroll);
var childToParentCoordinates = generalTransform.Transform(new Point(0, 0));
if (childToParentCoordinates.Y < 0)
{
this.ChildGrid.Children.Remove(this.TextHeader);
this.ParentGrid.Children.Add(this.TextHeader);
Grid.SetRow(this.TextHeader, 0);
}
}
else
{
var generalTransform = RichText.TransformToVisual(ParentScroll);
var childToParentCoordinates = generalTransform.Transform(new Point(0, 0));
if (childToParentCoordinates.Y > 0)
{
this.ParentGrid.Children.Remove(this.TextHeader);
this.ChildGrid.Children.Add(this.TextHeader);
Grid.SetRow(this.TextHeader, 1);
}
}
There may be less-hacky ways to reach the same results, but this solution seems to work smoothly in the emulator.
I've found a working solution myself... the complete detail is available on my blog here... it contains also the link to my demo project on GitHub.
The trick was to get hold of the VerticallScrollBar inside the ScrollViewer and to set the ManipulationMode to Control to get enough feedback on the UI thread.
With the scroll offset information of the scrollbar we than animate the specific ui element we want to keep in view.

How to disable right-button selection on List View Item (ListViewItem)?

I want to prevent the ability to deselect a list view item if it is already selected. Therefore how do I prevent right mouse click ability to deselect an item?
I have prevented the ability to deselect via swiping by using IsSwipeEnabled="False" on the List View. I didn't require swipe ability on the list view.
I'm happy to completely prevent right mouse click on the list view items if needed.
If I am reading your question correctly, it sounds like you want the ability for the user to select items, but to not be able to de-select. If that is the case, it seems like a strange requirement - it goes against normal UI convention and does something that the user is not expecting.
Having said that, you can do so by handling the SelectionChanged event in the ListView.
When the event is triggered, it gives you a list of removed (de-selected) items. You then just need to add those items back into the ListView's selected items list:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
itemListView.SelectedItems.Add(item);
}
}
Note that if you use the above code, you do not have to handle any swipe or mouse events.
Edit - Per OP's comment, the requirement is slightly different than what I thought:
I want the selected item to deselect if a different item is selected. however what I dont want is an already selected item to be (manually) deselected
Assuming that you have a single select ListView, you can still use the SelectionChanged event and the SelectionChangedEventArgs to do what you are asking for:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
{
var removed = e.RemovedItems[0];
itemListView.SelectedItem = removed;
}
}
I have found a simple solution on the following forum.
You simply add a RightTapped event to the ListView DataTemplate content.
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ContentPresenter RightTapped="daves_RightTapped" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And then in the code behind:
private void daves_RightTapped(object sender, Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
{
e.Handled = true;
}
This works fine on an outlook style ListView.

XAML/C#: What event fires after reordering a gridview?

I am making my first Windows Store app in Visual Studios 2012. I have a gridview control that I have enabled to be reordered. I have code that I need to run when the list is reordered. I have tried the Drop event. It does not fire. I tried several other drag events, which also did not fire. It seems like this should be so simple... Thanks for your time!
You cannot reorder a GridView unless the ItemsSource is bound to an ObservableCollection and CanReorderItems, CanDragItems, and AllowDrop are set to true. It is not necessary to use a CollectionViewSource to enable reordering in your gridview. In fact, a collectionviewsource is often used for grouping a gridview and reordering is not possible when data is grouped.
Anyway, your XAML would look like this:
<Grid Background="Black">
<Grid.DataContext>
<local:MyModel/>
</Grid.DataContext>
<GridView CanReorderItems="True" CanDragItems="True" AllowDrop="True"
ItemsSource="{Binding Items}">
</GridView>
</Grid>
Although any enumerable can be bound to the ItemsSource of a GridView it is only an ObservableCollection that enables reorder. Yes, you can use a custom type that implements reorder, but why mess with that when ObservableCollection does it for you?
Your view model might look like this:
public class MyModel
{
public MyModel()
{
foreach (var item in Enumerable.Range(1, 50))
Items.Add(item);
Items.CollectionChanged += Items_CollectionChanged;
}
ObservableCollection<int> m_Items = new ObservableCollection<int>();
public ObservableCollection<int> Items { get { return m_Items; } }
object m_ReorderItem;
int m_ReorderIndexFrom;
void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
m_ReorderItem = e.OldItems[0];
m_ReorderIndexFrom = e.OldStartingIndex;
break;
case NotifyCollectionChangedAction.Add:
if (m_ReorderItem == null)
return;
var _ReorderIndexTo = e.NewStartingIndex;
HandleReorder(m_ReorderItem, m_ReorderIndexFrom, _ReorderIndexTo);
m_ReorderItem = null;
break;
}
}
void HandleReorder(object item, int indexFrom, int indexTo)
{
Debug.WriteLine("Reorder: {0}, From: {1}, To: {2}", item, indexFrom, indexTo);
}
}
In the code above, the reorder event is not real. It is derived from a combination of the "Remove" action and the "Add" action in the CollectionChanged event. Here's why this is awesome. If the reorder was only available from the GridView then the ViewModel would not be able to handle it. Because the underlying list is how you detect reorder, the ViewModel is enabled.
Every case is slightly different. You may not care about the Index so you can simplify the code. You may not allow adding or removing from the collection so you only need to monitor the Add action. Again, it depends on your situation. My sample code above should get 99% of the cases taken care of.
Remember, GridView is not the only control that allows reorder. Any control based on ListViewBase (like the ListView) supports reorder - still using ObservableCollection. But GridView is the most common control to use this feature. For sure.
Reference: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.listviewbase.canreorderitems
Oh, to answer your question!
There is no event that indicates a reorder. Reorder is a derived action based on a combination of actions in the underlying ObservableCollection CollectionChanged event. Make sense?
By the way, here's sample syntax to bind to a CollectionViewSource, if you choose to:
<Grid Background="Black">
<Grid.DataContext>
<local:MyModel/>
</Grid.DataContext>
<Grid.Resources>
<CollectionViewSource x:Name="CVS" Source="{Binding Items}" />
</Grid.Resources>
<GridView CanReorderItems="True" CanDragItems="True" AllowDrop="True"
ItemsSource="{Binding Source={StaticResource CVS}}" >
</GridView>
</Grid>
Best of luck.