uwp - scrollviewer doesn't keep scroll position when content is updated - c#

I have this following XAML for my ScrollViewer
<!-- Scroller -->
<ScrollViewer x:Name="Scroller"
Margin="0 75 0 0"
Width="1080"
Height="560"
HorizontalScrollMode="Enabled"
HorizontalScrollBarVisibility="Auto"
HorizontalSnapPointsType="MandatorySingle"
HorizontalSnapPointsAlignment="Center">
<StackPanel x:Name="StackItems"
Orientation="Horizontal"
Background="Transparent"
HorizontalAlignment="Center"
Width="Auto"
Padding="920 0 0 0">
</StackPanel>
</ScrollViewer>
The problem is when I use method Insert of StackPanel to add a new item, the new item push the current view to either left or right according to the index I add.
For example:
There are 5 items in my StackPanel now, and I scrolled to the last index (4th), then while I am viewing index 4th and I added new Item to the first as shown below, my current view will be pushed to item 3rd and I need to scroll right once to get the 4th Item again.
this.StackItems.Children.Insert(0, new Item());
For this following matter, Could anyone tell me how to keep the current viewport active even if there is new item is added to the StackPanel ?
Appreciate that. Thank you!

There are two ways I can think of right away that use the ScrollViewer.
All methods below assume you know the index of the item you want to bring into view. If you don't know this you can use the current scrolllocation or selected item (in case of a ListView) to obtain this index.
The first method is by using ScrollViewer.ChangeView():
After you have added a new item to StackItems you can add this line of code:
Scroller.ChangeView(
StackItems.Padding.Left +
StackItems.Children
.Take(indexToBringIntoView)
.Sum(u => u.ActualSize.X),
null,
null
);
If the width of all items are equal you can instead just write of course:
Scroller.ChangeView(StackItems.Padding.Left + itemWidth * indexToBringIntoView, null, null);
The second method is by using UIElement.StartBringIntoView():
This method is easier than the first method, however I have noticed some strange behaviour of the ScrollViewer when using this.
After adding your new item to the StackItems you can use the following the bring you previous item into view:
StackItems.Children[indexToBringIntoView].StartBringIntoView();
Another way of achieving your goal is to use a horizontal ListView
You can set this up following the accepted answer from this SO thread.
Then if you have the index of the item you want to bring into view you can use ListView.ScrollIntoView(object item) like so:
listView.ScrollIntoView(Lister.Items[indexToBringIntoView]);
I have included this method as well since this is a very solid working method and I never had a problem with it.
If this is not what you are looking for, let me know and I'll adjust my answer.

Related

Is there a feature to shift item on top in a listview

I have a ListView which ItemSource is bind to an ObservableCollection. This collection stores a list of some user's chat messages. Now I want to shift that item on top whenever that user receives a new chat message. Likewise in WhatsApp and slack app. So I want to know what is the correct way of doing this. Right now I am removing an item from the list and then adding it to the 0th index but this way sometimes I am getting parameter incorrect issues. So is there any way so that I can directly shift any chat to the top without removing it.
Xaml code for listview is below:
<ListView MaxHeight="{x:Bind ViewModel.OpenedChatMaxHeight, Mode=OneWay}"
Margin="0,10,0,0"
CanDragItems="True"
Loaded="{x:Bind ViewModel.OpenedChatDataLoaded}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
SelectedIndex="{x:Bind ViewModel.OpenChatListSeletedItem,Mode=TwoWay}"
ItemsSource="{x:Bind ViewModel.OpenChatList,Mode=TwoWay}"
SelectionChanged="{x:Bind ViewModel.ChatSelected}"
IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.OpenPinnedChatListItemClick}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel ItemsUpdatingScrollMode="KeepItemsInView" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
The ObservableCollection<T> class has a Move method that you can use to move an item from one index to another:
OpenedChatMaxHeight.Move(oldIndex, 0);
This is more convenient than manually removing and adding the item back at index 0.
I have tried the Move method but in that case, I always get index out of range error my view just gets out of focus.
Move method will moves the item at the specified index to a new location in the collection. Please add debug point to check if the oldIndex is correct. Above problem looks you have not get correct oldindex.
Is there a feature to shift item on top in a listview
For fixing the chart item in the top, we often make a separate top list. When you want to fix the chart item, you could remove it from the previous chart list, and add it into top list.
For this way, it is easy to make a istop style, but not make datatemplete selector for previous list. And you could also store the istop chart items in local storage to improve rendering performance.

UWP - FlipView - Need FlipView to only load a single item content

I have been struggling for the last week trying to implement a FlipView in the like of the Microsoft News UWP app.
I tried the following: a FlipView With an ItemTemplate and an ObservableCollection with my items.
However this caused the whole lot of items within my ObservableCollection to be pre-loaded, which is not what I want.
Has anyone got any tip allowing me to change this behaviour of the FlipView to be as follows: Only the first item is loaded, then when I click the arrow, the next item gets loaded.
Thanks in advance :)
UPDATE:
I can confirm that the behaviour is as #Sunteen Wu - MSFT said.
I also tried using a similar logic to the one on fund the demo by #Martin Zikmund, however, it was not clearing the previous item
My goal is to load 1 single item at a time. The behaviour like so:
1) I see 1 item
2) If I click the > (next) arrow:
* this item is unloaded from the flipview
* the next item is loaded
3) if I click the < (previous) arrow:
* this item is unloaded from the flipview
* the previous item is re-loaded
Hope someone can help, I really want to give that feature to my users ^^.
However this caused the whole lot of items within my ObservableCollection to be pre-loaded
FlipView control's ItemsPanelTemplate contains VirtualizingStackPanel. So the FlipView control supports virtualization at default. You can check the visual tree when you debugging the app, you may find no matter how many items you bind to the FlipView, it only load three FlipViewItem at default.
So in my opinion, the FlipView will not pre-loaded the whole items of your ObservableCollection , by default it may load the previous one, current one and the next one, unless you change the ItemPanel to StackPanel which doesn't support virtualization.
<FlipView x:Name="flipView1" Width="480" Height="270"
BorderBrush="Black" BorderThickness="1">
<FlipView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</FlipView.ItemsPanel>
<FlipView.ItemTemplate>
...
</FlipView.ItemTemplate>
</FlipView>
Only the first item is loaded, then when I click the arrow, the next item gets loaded.
As FlipView using virtualization so that I guess you don't still need this feature. If you still want it, you can try to bind source to the FlipView with instance data is empty and every time the selection changed, force load the content manually. For this you may reference this similar thread that #Martin Zikmund provides a demo for single loading.
Additionally, according to Do's and don'ts remarks of FlipView:
Avoid using a flip view control for larger collections, as the repetitive motion of flipping through each item can be tedious.
If you do have a larger collection for binding, avoid using a flip view but consider a List view or grid view.

Dropping item between listviews animation

I have 2 ListView controls bound to 2 different ObservableCollection<string> like this:
<ListView x:Name="List1"
Grid.Column="0"
CanDragItems="True"
CanReorderItems="True"
AllowDrop="True"/>
<ListView x:Name="List2"
Grid.Column="1"
CanDragItems="True"
CanReorderItems="True"
AllowDrop="True"/>
When dragging and dropping items in their own respective lists I get the default animation like this:
But I cannot get the same animation when I hover over the second ListView. Why is that and how can I invoke the same animation in this case? I can handle what happens on drop but first I need to be able to invoke the same animation.
All current solution I have gone through does not have the animation or uses some other library/control to substitute for this. Any help or suggestion regarding this is appreciated.
There is no default drop animation for a ListView. You'll need to create something yourself - or use a third party solution such as you report to have already found.

WPF: Which solution? TabControl with close button and new tab button

I'm trying to find the best solution for a TabControl that both support a close button on each TabItem, and always show a "new tab button" as the last tab.
I've found some half working solutions, but i think that was for MVVM, that I'm not using. Enough to try to understand WPF =)
This is the best solution I've found so far:
http://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF
A solution that i actually understand. But the problem is that it is using the ItemsSource, and i don't want that. I want to bind the ItemsSource to my own collection without having to have special things in that collection to handle the new tab button.
I've been search for days now but cant find a good solution.
And I'm really new to WPF, otherwise i could probably have adapted the half done solutions I've found, or make them complete. But unfortunately that is way out of my league for now.
Any help appreciated.
I have an open source library which supports MVVM and allows extra content, such as a button to be added into the tab strip. It is sports Chrome style tabs which can tear off.
http://dragablz.net
This is bit of a dirty way to achieve the Add (+) button placed next to the last TabItem without much work. You already know how to place a Delete button next to the TabItem caption so I've not included that logic here.
Basically the logic in this solution is
To bind ItemsSource property to your own collection as well as
the Add TabItem using a CompositeCollection.
Disable selection of
the Add(+) TabItem and instead perform an action to load a new tab when it
is clicked/selected.
XAML bit
<TextBlock x:Name="HiddenItemWithDataContext" Visibility="Collapsed" />
<TabControl x:Name="Tab1" SelectionChanged="Tab1_SelectionChanged" >
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.MyList, Source={x:Reference HiddenItemWithDataContext}}" />
<TabItem Height="0" Width="0" />
<TabItem Header="+" x:Name="AddTabButton"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
The code behind
private void Tab1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(AddTabButton))
{
//Logic for adding a new item to the bound collection goes here.
string newItem = "Item " + (MyList.Count + 1);
MyList.Add(newItem);
e.Handled = true;
Dispatcher.BeginInvoke(new Action(() => Tab1.SelectedItem = newItem));
}
}
You could make a converter which appends the Add tab. This way the collection of tabs in you viewmodel will only contain the real tabs.
The problem is then how to know when the Add tab is selected. You could make a TabItem behavior which executes a command when the tab is selected. Incidentally I recommended this for another question just recently, so you can take the code from there: TabItem selected behavior
While I don't actually have the coded solution, I can give some insight on what is most likely the appropriate way to handle this in a WPF/MVVM pattern.
Firstly, if we break down the request it is as follows:
You have a sequence of elements that you want to display.
You want the user to be able to remove an individual element from the sequence.
You want the user to be able to add a new element to the sequence.
Additionally, since you are attempting to use a TabControl, you are also looking to get the behavior that a Selector control provides (element selection), as well as an area to display the element (content) which is selected.
So, if we stick to these behaviors you'll be fine, since the user interface controls can be customized in terms of look and feel.
Of course, the best control for this is the TabControl, which are you already trying to use. If we use this control, it satisfies the first item.
<TabControl ItemsSource="{Binding Path=Customers}" />
Afterwards, you can customize each element, in your case you want to add a Button to each element which will execute a command to remove that element from the sequence. This will satisfy the second item.
<TabControl ...>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CustomerId}" />
<Button Command="{Binding Path=RemoveItemCommand, Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
<TabControl.ItemTemplate>
</TabControl>
The last part is a bit more difficult, and will require you to actually have to create a custom control that inherits from the TabControl class, add an ICommand DependencyProperty, and customize the control template so that it not only displays the TabPanel, but right next to it also displays a Button which handles the DependencyProperty you just created (the look and feel of the button will have to be customized as well). Doing all of this will allow you to display your own version of a TabControl which has a faux TabItem, which of course is your "Add" button. This is far far far easier said than done, and I wish you luck. Just remember that the TabPanel wraps onto multiple rows and can go both horizontally or vertically. Basically, this last part is not easy at all.

ListView : how to always scroll to the bottom

I have a list view that I want to scroll to the bottom as I add items into the "Items" list.
As I add items they appear in the ListView, but when I reach the limit of the screen, the list remains showing the top section and new items are added to the bottom. If I scroll down I can see the new items. I'd like it to auto scroll to the bottom so that I can always see the latest items in the list.
<ListView
x:Name="lvBasketContent"
Grid.Row="1"
ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource ListViewItemStyle1}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Bottom"
SelectionMode="None"
IsSwipeEnabled="False"
VerticalAlignment="Top"
>
Can anyone help me out please?
You need to create a custom behavior or derived implementation of ListView.
This class should monitor the ItemsSource collection for changes and call ListViewBase.ScrollIntoView(Object), passing in the item that you want to show. In your case, this may be the last one added.
I recommended the behavior as it keeps your code modular as you can use it on any listview in your solution by changing the xaml only.
I'm not going to write the code for you as behaviors are a very useful technique to learn first hand. The first link should give you all you need to get cracking.
Do you can try put this in your code ? Each time you add an item to your listbox , try call it
//Add an item in the listbox
lvBasketContent.Items.Add(...);
//...
//Scroll to bottom
lvBasketContent.SelectedIndex = lvBasketContent.Items.Count -1
lvBasketContent.ScrollIntoView(lvBasketContent.SelectedItem)

Categories

Resources