Scroll a ListBox's VirtualizingStackPanel with buttons - c#

I have a Listbox displaying 5 items at a time, horizontally. I want to implement a 'Previous' and 'Next' button that when clicked, will display the previous set of 5, or next set of 5 items. I am not displaying the horizontal scroll bar, as I don't want a user to navigate with that.
Is there already some functionality implemented so I can do this, or do I need to write code to calculate what items I'm displaying, etc, in the button's click events?
Thanks in advance!

You can use the built-in ScrollViewer.PageUp() and ScrollViewer.PageDown() commands, like this:
public void ShowNextPage()
{
InvokeOnScrollViewer(listBox, viewer => viewer.PageDown());
}
public void ShowPriorPage()
{
InvokeOnScrollViewer(listBox, viewer => viewer.PageUp());
}
public void InvokeOnScrollViewer(ItemsControl control, Action<ScrollViewer> action)
{
for(Visual vis = control as Visual; VisualTreeHelper.GetChildCount(vis)!=0; vis = VisualTreeHelper.GetChild(vis, 0))
if(vis is ScrollViewer)
{
Action((ScrollViewer)vis);
break;
}
}
How it works: InvokeOnScrollViewer scans down the visual tree until it finds the ScrollViewer, then invokes the given action on it, which is either PageUp() or PageDown().
When your ItemsPanel is a StackPanel (of either orientation, virtualizing or not), ScrollViewer.PageUp() moves back by one viewport and ScrollViewer.PageDown() moves forward by one viewport. In other words, if your ListBox shows five items then these commands move it by five items.

Look at ListBox.ScrollIntoView() method.

Related

Xamarin forms scroll to top of listView which is inside a tabbed page by tapping Tabbed Page Top Bar

Current Situation
I have made a xamarin forms cross-platform app which has a tabbed Page with an infinite scroll listView.
The listView is populated in increments of 10 items whenever the last item appears.
The normal behaviour
Usually when we tap the top bar of the tabbed page (the bar containing the tabbed page's Title) for the first time (if we are not viewing the tab at that moment), the app will switch to view that page. Which is fine.
When we tap the top bar for the second time (when tapping the top bar of current viewing tabbed page), nothing happens (unlike twitter, u can tap to scroll to the top )
My Problem
I want to be able to make the listView to scroll back to the top (to the first Item on the list) when the top bar is tapped (something like how twitter does in its android app) while still maintaining all the already loaded items in my listView.
Any idea on how to make that top bar react to the tap to scroll the listView back to the top?
The TabbedPage exposes a CurrentPageChanged event (or you can override OnCurrentPageChanged) that fires only when the selected tab changes, so you'll need to subscribe to that if you want to scroll when the user changes tabs. It doesn't seem like this is needed, but just in case...
Xamarin.Forms doesn't have a built-in event that fires when the already-selected tab is selected a second time. You can make that happen with custom renderers though, by subclassing Xamarin's TabbedPage renderers. I wrote it up here and have a working Xamarin.Forms solution for iOS, Android, and UWP here.
You don't mention the platforms, so assuming you want iOS and Android, the core bits are below.
iOS custom renderer
public class MainTabPageRenderer : TabbedRenderer
{
private UIKit.UITabBarItem _prevItem;
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
if (SelectedIndex < TabBar.Items.Length)
_prevItem = TabBar.Items[SelectedIndex];
}
public override void ItemSelected(UIKit.UITabBar tabbar,
UIKit.UITabBarItem item)
{
if (_prevItem == item && Element is MainPage)
{
// the same tab was selected a second time, so do something
}
_prevItem = item;
}
}
Android custom renderer
public class MainTabPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
if (Element is MainPage)
{
// the same tab was selected a second time, so do something
}
}
}
Once you have captured that event, use the ListView.ScrollTo method to scroll to the top.
You should try something like this for scrolling up
ListViewName.ScrollTo(yourobject,ScrollToPosition.Start, true);
// true means boolean for animated.
I hope this help you

How to make the CommandBar open towards the bottom of the screen?

I'm having what's probabily a stupid doubt, but I really don't know how to fix this thing.
I have a complex UI with many items inside it (a SplitView with other stuff inside it, then a Frame which holds a Page, where I have a Grid and finally my CommandBar).
When I tap on the "..." button, the CommandBar opens towards the top of the window, and it also actually covers part of the UI outside of the Frame where its parent Page is located (I don't event know how's that possible).
I tried setting the VerticalAlignment property of both the CommandBar, the parent Grid, the Page etc... to Top, but the CommandBar still opens towards the top of the screen.
Is there a way to have it open towards the bottom, just like the CommandBar in the built in Weather or Photo app? Am I missing a setting/property here?
Thank you for your help!
Sergio
Edit: just to be clear, the basic structure for my page is something like this:
RootContent > ... > Frame > Page > Grid > CommandBar
I can't put the CommandBar inside the Page.TopAppBar as if I do that the CommandBar gets placed outside of my Page and covers the top of my UI. I need the CommandBar to be place inside the Page.
The CommandBar relies on VisualStates to control this part of its dimensions and animations this makes it easy to use a custom visual state manager to intercept the state change calls and replace all OpenUp calls with OpenDown ones.
public class OpenDownCommandBarVisualStateManager : VisualStateManager
{
protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
{
//replace OpenUp state change with OpenDown one and continue as normal
if(!string.IsNullOrWhiteSpace(stateName) && stateName.EndsWith("OpenUp"))
{
stateName = stateName.Substring(0, stateName.Length - 6) + "OpenDown";
}
return base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);
}
}
public class OpenDownCommandBar : CommandBar
{
public OpenDownCommandBar()
{
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var layoutRoot = GetTemplateChild("LayoutRoot") as Grid;
if(layoutRoot != null)
{
VisualStateManager.SetCustomVisualStateManager(layoutRoot, new OpenDownCommandBarVisualStateManager());
}
}
}
Then just use the new OpenDownCommandBar instead of the normal one.
<myControls:OpenDownCommandBar>...</myControls:OpenDownCommandBar>
How are you defining your command bar? There are two ways to do so:
<Page.BottomAppBar>
<CommandBar>
....
</CommandBar>
</Page.BottomAppBar>
Or on the contrary..
<Page.TopAppBar>
<CommandBar>
....
</CommandBar>
</Page.TopAppBar>
In your case you would want to use the former and make sure your appbar is not inside any other containers.
You're using the commandbar for something it wasn't intended for. It's supposed to cover page content when displayed.
If you can't change the way something behaves (as in this case) then the alternative would be a different control (there isn't anything I'm aware of which would give you exactly what you want) or make your own.

Bring button to front on Windows Phone UI

On the main page of my app I have a grid with 6 x 4 columns and rows of buttons.
I want to move one of these buttons to the middle and then scale it larger using RadControls by Telerik.
I can do this easily however when I do the button is shown behind all the other buttons on the grid and I can't seem to make it come to the front.
Any help would be much appreciated.
The order that items were added to a panel/grid determines what control is above another. Looking around it looks like you have two options:
Change the Canvas.ZIndex for the button you want to be on top.
Yes it seems odd as there is no canvas, but it works for any panel or grid.
Remove and re-add the child from the parent grid so that it was last. I found a nice snippet of code here posted by "CleverCoder" : http://forums.silverlight.net/post/63607.aspx
//Originally posted by CleverCode - http://forums.silverlight.net/post/63607.aspx
public static void PushToTop(this FrameworkElement element)
{
if (element == null) throw new ArgumentNullException("element");
var parentPanel = element.Parent as Panel;
if (parentPanel != null)
{
// relocate the framework element to be the last in the list (which makes it "above" everything else)
parentPanel.Children.Remove(element);
parentPanel.Children.Add(element);
parentPanel.UpdateLayout();
}
}

How to reorder items by Drag and Drop inside a big vertical list while maintaining vertical scrollability?

There are lots of tips around Drag and Drop on the Windows Phone, but I am currently unable to put everything together. So I hope you can give me some advice to reach my goal: Display a scrollable list of items with good reordering and scroll experience.
I use a StackPanel to present a vertical list of controls. Let's say these controls are CheckBoxes displaying some information (in reality I created a bit more complex custom controls). There can be lots of items so I put a ScrollViewer around the StackPanel so the user can scroll up and down. But now I also want to give the user the opportunity to reorder the controls in the list via Drag and Drop.
Several things are unclear for me:
How do I enable Drag and Drop functionality in the StackPanel? (So it looks smooth and the items change position in an animated, nice to look at, way; they should keep making space for the item-to-be-inserted while the user drags it around.)
How can I achieve that the user can vertically scroll the list while still being able to Drag and Drop items? (I think there could be a special "drag spot" on every item the user has to drag at, so I can differentiate between dragging and scrolling.)
How do I auto-scroll the list when the user drags one item to the upper or lower border if the list is bigger than the screen?
Is this even the right combination of controls? Is there a better one? (But I don't want to calculate item positions manually.)
I'd love to hear your ideas on this topic, any help is greatly appreciated!
Hi you could check this out, might be what you're looking for:
http://blogs.msdn.com/b/jasongin/archive/2011/01/03/wp7-reorderlistbox-improvements-rearrange-animations-and-more.aspx
You can refer this link. This has a nice reordering of listbox with vertical scrolling. Hold the item to be dragged for 1 min and start dragging.
The answer you seek is the ReorderListBox control developed by Jason Ginchereau.
I'm going to show a quick implementation of it, but if you want a complete demo, then download the source from CodePlex here.
First, install the control from Nuget:
Tools >>> Library Package Manager >>> Manage NuGet Packages for Solution...
Search for ReorderListBox, and install the one created by Jason Ginchereau
Then, in the XAML of your app's start page (ie. MainPage.xaml), copy and paste the highlighted assembly reference into the phone:PhoneApplicationPage tag at the top where the other assembly references are located.
xmlns:rlb="clr-namespace:ReorderListBox;assembly=ReorderListBox"
Next, drop this into your XAML page
<rlb:ReorderListBox
x:Name="reorderListBox"
Grid.Row="2"
Margin="12,0,12,12"
IsReorderEnabled="True">
<rlb:ReorderListBox.ItemTemplate>
<DataTemplate>
<TextBlock
Margin="12,4,12,4"
FontSize="36"
Text="{Binding}" />
</DataTemplate>
</rlb:ReorderListBox.ItemTemplate>
</rlb:ReorderListBox>
Finally, in your code-behind (ie MainPage.xaml.cs), you want to define an ObservableCollection with your list of data and assign it to the reorderListBox.ItemsSource. You may also want to save the state of the list after it has been resorted for the next time the application is opened. Here's an example:
public partial class MainPage : PhoneApplicationPage
{
public ObservableCollection<string> SampleDataList { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
if (IsolatedStorageSettings.ApplicationSettings.Contains("SampleDataList"))
{
SampleDataList = IsolatedStorageSettings.ApplicationSettings["SampleDataList"] as ObservableCollection<string>;
}
else
{
SampleDataList = new ObservableCollection<string>();
SampleDataList.Add("Zero");
SampleDataList.Add("One");
SampleDataList.Add("Two");
SampleDataList.Add("Three");
SampleDataList.Add("Four");
SampleDataList.Add("Five");
SampleDataList.Add("Six");
SampleDataList.Add("Seven");
SampleDataList.Add("Eight");
SampleDataList.Add("Nine");
SampleDataList.Add("Ten");
SampleDataList.Add("Eleven");
SampleDataList.Add("Twelve");
SampleDataList.Add("Thirteen");
SampleDataList.Add("Fourteen");
}
reorderListBox.ItemsSource = SampleDataList;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
IsolatedStorageSettings.ApplicationSettings["SampleDataList"] = SampleDataList;
IsolatedStorageSettings.ApplicationSettings.Save();
}
}

Bring Winforms control to front

Are there any other methods of bringing a control to the front other than control.BringToFront()?
I have series of labels on a user control and when I try to bring one of them to front it is not working. I have even looped through all the controls and sent them all the back except for the one I am interested in and it doesn't change a thing.
Here is the method where a label is added to the user control
private void AddUserLabel()
{
var field = new UserLabel();
userContainer.Controls.Add(field);
SendLabelsToBack(); // Send All labels to back
userContainer.Controls[field.FieldName].BringToFront();
}
Here is the method that sends all of them to the back.
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls);
label.SendToBack();
}
Yeah, there's another way. The Controls.SetChildIndex() also changes Z-order. The one with index 0 is the one on top. Doesn't buy you anything though, BringToFront() uses this method.
Your SendLabelsToBack() method as given cannot work, it will also send the label to added to the back. But your next statement fixes that again.
Okay, that doesn't work, which means the BringToFront() method doesn't get executed. Look in the Output window for a "first chance exception" notification. As written, your SendLabelsToBack() will cause an exception if the user control contains any control other than a UserLabel. Also, set a breakpoint after the BringToFront() call and check the value of userContainer.Controls[0].Name when it breaks.
Controls' z-index is per-container.
If you call BringToFront on a control that is inside a container (such as a Panel), it will not bring the container to the front.
Therefore, the control will only go in front of other controls in that container.
To see what containers your controls are in, you can use the Document Outline pane in the View menu.
EDIT: Your userContainer control is probably behind a different control.
Have you tried Invalidate() after BringToFront()? BringToFront does not raise the Paint event
try this:
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls)
{
label.SendToBack();
label.Invalidate();
}
}
I think you just need to change your last line:
userContainer.Controls[field.FieldName].BringToFront();
to this:
userContainer.Controls[field.Name].BringToFront();
When you use a string as the indexer for the Controls collection, it goes by the Name property of the control (not the FieldName property).
Since you're just trying to bring the most recently-added control to the top, this would also work:
userContainer.Controls[userContainer.Controls.Count - 1].BringToFront();
From my experience looks like windows puts all controls belonging to one graphic container(pane, group box...etc) in a software collection. The collection is ordered by child index which is a property of every control in that container.
The trick is that children with the same index can and do exists. In this case windows will paint those children ordered relative to others but between them it will paint them in the reverse order they had been added to the container.
Long story short: for one container-you need to make sure controls have different indexes by changing ALL NOT just SOME of the indexes when you want to change the z-order. For example:
foreach (Control newControl in TopControl.Controls)
{
TopControl.Controls.SetChildIndex(newControl,indexlogic(newControl));
}
where indexLogic(newControl ) is your method of calculation of the index of particular control.

Categories