Scroll to item in listview in Silverlight 3 - silverlight

I have a silverlight 3 app with a textbox on the main window and a childwindow that has a list of all the potential textbox values. When I open that childwindow I want it to scroll to the correct one in the list.
I'm trying to do this with the code below...using the ScrollIntoView. It was not working at all until I add the UpdateLayerout(). However it does not seem to work all the time. At times it scrolls but not all the way to the item, it is a few items higher than it should be. The listbox is in an Accordion and the list items use a ItemTemplate\DataTemplate, not sure if that effects anything but thought I'd mention it.
Any ideas what I'm missing in the code below?
What I would like is to scroll the item to the top of the list ....any ideas how to that?
(Or any other suggestions on how to code this better)
Thanks!
for (int index = 0; index < myList.Items.Count; index++) {
object obj = myList.Items[index];
var listItem= obj as listItemObject;
if (listItemObj != null) {
if (string.Compare(listItemObj.id, _PastedInId, StringComparison.InvariantCultureIgnoreCase) == 0) {
selectThisIndex = index;
scrollToThisItem = obj;
}
}
}
myList.SelectedIndex = selectThisIndex;
if (scrollToThisItem != null){
myList.UpdateLayout();
myList.ScrollIntoView(scrollToThisItem);
}

Consider using the ItemsControlExtensions implementation from the Silverlight Toolkit, available at http://silverlight.codeplex.com/sourcecontrol/network/Show?projectName=Silverlight&changeSetId=47051#637494
This has a ScrollIntoView(FrameworkElement element) method that may help.

Related

WPF VirtualizingWrapPanel Selected Item Change cause scroll

I have implemented a virtualizing panel that works pretty well, the panel implements IScrollInfo. I am in the process of getting it to work using a keyboard. The Panel is used in a ListView as the ListView.ItemsPanel. When the user presses down the selected item correctly changes but no scrolling occurs and when you reach the last visible item you can not press down any longer. I am trying to figure out how to have the panel aware of the selected item and scroll appropriately. I have tried searching around but I cant seem to find anything related to what I want to do. A point in the right direction would be greatly appreciated.
Edit: Here is the code for the VirtualizingWrapPanel
So I found a solution that works fairly well, I am not sure if it is the best route to take but it seems to work fine.
In the ArrangeOverride method while looping over the children I do the following.
var listViewItem = child as ListViewItem;
if (listViewItem != null)
{
listViewItem.Selected -= ListViewItemSelected;
listViewItem.Selected += ListViewItemSelected;
}
In MeasureOverride I make a call to CleanUpItems in here we will need to unsubscribe from the selected event.
var listViewItem = children[i] as ListViewItem;
if (listViewItem != null)
{
listViewItem.Selected -= ListViewItemSelected;
}
I also added the following three functions.
private void ListViewItemSelected(object sender, RoutedEventArgs e)
{
var listViewItem = sender as ListViewItem;
if(listViewItem == null) return;
var content = listViewItem.Content as CollectionViewGroup;
if(content != null) return; //item is a group header dont click
var items = ItemContainerGenerator as ItemContainerGenerator;
if(items == null) return;
BringIndexIntoView(items.IndexFromContainer(listViewItem));
listViewItem.Focus();
}
protected override void BringIndexIntoView(int index)
{
var offset = GetOffsetForFirstVisibleIndex(index);
SetVerticalOffset(offset.Height);
}
private Size GetOffsetForFirstVisibleIndex(int index)
{
int childrenPerRow = CalculateChildrenPerRow(_extent);
var actualYOffset = ((index / childrenPerRow) * ChildDimension.Height) - ((ViewportHeight - ChildDimension.Height) / 2);
if (actualYOffset < 0)
{
actualYOffset = 0;
}
Size offset = new Size(_offset.X, actualYOffset);
return offset;
}
The GetOffsetForFirstVisibleIndex function will probably vary depending on your implementation but that should be enough info if anyone else is having trouble coming up with a solution.

Visual Tree Finder is returning null while searching for Data Grid

I have Tab Control which has many tab items, I am checking Data Grid Items Count while closing tab items. For the first time it works fine(I mean in first iteration). After closing one tab item, in second iteration sellDtg is null. Does anyone know why it is happening? I am concerning that this is UI problem, layout is not being refreshed. Please help me or show direction.
while (tc.HasItems)
{
TabItem ti = tc.SelectedItem as TabItem;
if (ti.Header == "Продажа")
{
Microsoft.Windows.Controls.DataGrid sellDtg = FindChild<Microsoft.Windows.Controls.DataGrid>(tc, "SellDataGrid");
if (sellDtg.Items.Count > 0)
{
Sell sl = new Sell();
if (Sell.basketfromSellDateListBox == false)
{
sl.ClearBasket(sellDtg);
Sell.ClearFromSellBasket((int)sellDtg.Tag);
}
}
}
if (ti != null)
tc.Items.Remove(ti);
}
Thanks in advance!!!
I've written a simple FindChildLogical function in analogy for LogicalTreeHelper below:
public static T FindChildLogical<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
if (parent == null) return null;
var child = LogicalTreeHelper.FindLogicalNode(parent, childName);
return (T)child;
}
and you call it as:
Microsoft.Windows.Controls.DataGrid sellDtg = FindChildLogical<Microsoft.Windows.Controls.DataGrid>(ti, "SellDataGrid");
I hope it gets you where you intend to.
I am going to assume your FindChild method uses the VisualTreeHelper to find its children.
In the first iteration, the TabItem's Content has been through a layout pass, and is visible. This means that the TabItem's Content will be in the visual tree.
However, for the other tab items, their Content hasn't been through a layout pass (it is only added to the visual tree when it's parent gets selected, and this has to then go through a layout/render pass), and won't be in the visual tree.
There are a couple of ways to get the child content of a TabItem that hasn't been through a layout pass as the selected tab:
1) You can try using the LogicalTreeHelper to find the Grid you're looking for (and you will likely have to search the Content of the TabItem, not the TabControl).
2) You can take your code out of the while loop, and do a callback on the dispatcher at the Loaded priority:
void RemoveAllItems()
{
if (!tc.HasItems) return;
TabItem ti = tc.SelectedItem as TabItem;
if (ti.Header == "Продажа")
{
var sellDtg = FindChild<Microsoft.Windows.Controls.DataGrid>(tc, "SellDataGrid");
if (sellDtg.Items.Count > 0)
{
Sell sl = new Sell();
if (Sell.basketfromSellDateListBox == false)
{
sl.ClearBasket(sellDtg);
Sell.ClearFromSellBasket((int)sellDtg.Tag);
}
if (ti != null)
tc.Items.Remove(ti);
}
}
Dispatcher.BeginInvoke(new Action(RemoveAllItems), DispatcherPriority.Loaded);
}
If you use the second method, you will likely be able to see the tab items removed one at a time, which may be something you don't want to see.

Cant see controls inside User Control in the VisualTreeHelper

I have UserControl in wpf 4.0 which contains buttons , labels , textboxes etc....
I want to loop those controls and when I get a buuton , I want to take it's name and save it to my list . Basically , all I want to do is to create a Names_list of all my buttons in the UserControl.
I have a method that iterates all the controls and if it finds a button , it saves it's name -
public void EnumVisual(Visual myVisual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
Button _button = childVisual as Button;
if (_button != null)
{
Class_Button _newButtonClass = new Class_Button();
if (_button.Name != null)
{
_newButtonClass.ButtonName = _button.Name;
}
ButtonsList.Add(_newButtonClass);
}
// Enumerate children of the child visual object.
EnumVisual(childVisual);
}
}
I always get an empty list.
When I enter in to the code by debugging it and I watch the VisualTree of my UserControl , I see all the Panels and GroupBoxes and Grids but I dont see buttons , labels and texboxes although every control has a x:Name and every control is x:FieldModifier="public". This is very odd....And I cant understand the reason for that as well as how to solve this problem...
can anyone tell what I am doing wrong?
thanks
As suggested by #GazTheDestroyer you want to make sure the control template has been applied before trying to use VisualTreeHelper. Try:
public void EnumVisual(Visual myVisual)
{
if(myVisual is FrameworkElement)
((FrameworkElement)myVisual).ApplyTemplate();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
Button _button = childVisual as Button;
if (_button != null)
{
Class_Button _newButtonClass = new Class_Button();
if (_button.Name != null)
{
_newButtonClass.ButtonName = _button.Name;
}
ButtonsList.Add(_newButtonClass);
}
// Enumerate children of the child visual object.
EnumVisual(childVisual);
}
}
You can use a tool like Snoop
or WPF Inspector
to examine the visual tree of your control.
If these tools are able to do so, the error must be somewhere in your code, right?

How to goto particular page number in flowdocument reader through code in WPF?

There is a pagenumber property in flowdocument reader.But that property is readonly. Is there any way to goto particular page number in flowdocument reader.Please help.
Thanks.
If you keep track of the Blocks on the FlowDocument contained in the FlowDocumentReader,
than you can simply use:
// Getting a block by index
YourReader.Document.Blocks.ElementAt(index).BringIntoView();
// Showing Last Block
YourReader.Document.Blocks.LastBlock.BringIntoView();
// Showing the last Inline
(YourReader.Document.Blocks.LastBlock as Paragraph).Inlines.LastInline.BringIntoView();
This works only on the page ViewingModes of the FlowDocumentReader.
if you whould like to do so on the scroll mode, you must go down the visual tree and search for the ScrollViewer,
somthing like this:
public static ScrollViewer FindScroll(Visual visual)
{
if (visual is ScrollViewer)
return visual as ScrollViewer;
ScrollViewer searchChiled = null;
DependencyObject chiled;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
chiled = VisualTreeHelper.GetChild(visual, i);
if (chiled is Visual)
searchChiled = FindScroll(chiled as Visual);
if (searchChiled != null)
return searchChiled;
}
return null;
}
ScrollViewer scroller = FindScroll(YourReader as Visual);
if (scroller != null)
(scroller as ScrollViewer).ScrollToBottom();
If you are willing to restrict your users to paged display, use FlowDocumentPageViewer instead: this has a GoToPage() method. For some reason GoToPage() doesn't seem to be offered on FlowDocumentReader; I'd guess this is because FlowDocumentReader isn't always in a mode where paging is meaningful (the user can select a continuous scrolling view), and provides its own UI for this when it is meaningful.
You could try sending it the NavigationCommands.GoToPage command, but this is only documented as working on FlowDocumentPageViewer and DocumentViewer; I haven't tested it on FlowDocumentReader.

Automatic Scrolling in a Silverlight List Box

How can I programmatically force a silverlight list box to scroll to the bottom so that the last item added is always visible.
I've tried simply selecting the item. It ends up as selected but still not visible unless you manually scroll to it.
Use the ListBox's ScrollIntoView method passing in the last item. You may need to call UpdateLayout immediately before it for it to work.
The ScrollIntoView() method will scroll the last item into view, however listBox.UpdateLayout() must be called just before ScrollIntoView(). Here is a complete method with code:
// note that I am programming Silverlight on Windows Phone 7
public void AddItemAndScrollToBottom(string message)
{
string timestamp = DateTime.Now.ToString("mm:ss");
var item = new ListBoxItem();
item.Content = string.Format("{0} {1}", timestamp, message);
// note that when I added a string directly to the listbox, and tried to call ScrollIntoView() it did not work, but when I add the string to a ListBoxItem first, that worked great
listBoxEvents.Items.Add(item);
if (listBoxEvents.Items.Count > 0)
{
listBoxEvents.UpdateLayout();
var itemLast = (ListBoxItem)listBoxEvents.Items[listBoxEvents.Items.Count - 1];
listBoxEvents.UpdateLayout();
listBoxEvents.ScrollIntoView(itemLast);
}
}
Slightly refactored to reduce the lines of code:
listBoxEvents.Add(item)
listBoxEvents.UpdateLayout()
listBoxEvents.ScrollIntoView(listBoxEvents.Items(listBoxEvents.Items.Count - 1))
Just went through this and none of the solutions above worked in a Silverlight 5 app. The solution turned out to be this:
public void ScrollSelectedItemIntoView(object item)
{
if (item != null)
{
FrameworkElement frameworkElement = listbox.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (frameworkElement != null)
{
var scrollHost = listbox.GetScrollHost();
scrollHost.ScrollIntoView(frameworkElement);
}
}
}

Resources