WPF ListBox - Scroll always on top - wpf

I have a ListBox that displays some words. Words are entered in TextBox, and when submitted on button click, they are added to ListBox. The problem is, if I add many words, scroll is always on top of ListBox, so I don't see last but first words added. Is there a way to dynamically move scroll to the end of ListBox every time word is added, so last added word is visible?

here you go, this should do nicely...
public static void ScrollToBottom(this ListBox listbox)
{
if (listbox == null) throw new ArgumentNullException("listbox", "Argument listbox cannot be null");
if (!listbox.IsInitialized) throw new InvalidOperationException("ListBox is in an invalid state: IsInitialized == false");
if (listbox.Items.Count == 0)
return;
listbox.ScrollIntoView(listbox.Items[listbox.Items.Count - 1]);
}
Now, given any ListBox I can do this:
ListBox lb = ...;
lb.ScrollToBottom();

Related

WPF Treeview - How can I scroll the treeview until the selected item is on top?

I am currently working on an wpf application where I have a treeview with a hierarchichal datatemplate with my own object "Workstep".
What I want to do:
I want to hit a specific key (letter) and bring the first workstep with the a name starting with that letter on top of my treeview
Why:
Because the treeview is large and scrolling until the specific letter takes a lot of time in the production area of the company i am working for
My work until now:
In my viewmodel I catch the keydown event of my treeview with the following method (watch out-> the "K" is just a example letter to show what I mean):
Public Sub TreeViewKeyDown(sender as Object, e as KeyEventArgs)
if e IsNot Nothing AndAlso e.Key = Key.K Then
For Each w In myTree
If w.Name.StartsWith("K") Then
Dim treeViewItem As TreeViewItem = CType(m_TreeViewInstance.ItemContainerGenerator.ContainerFromItem(w), TreeViewItem)
treeViewItem.BringIntoView()
End If
Next
End If
The problem with my current solution is that my items come to view but are not on top of my treeview, as I would like to have.
Does anybody have a clue how to do that?
(btw: Didn't get the answer through this article: Treeview -- How to scroll until selected item is on top?)
I achieve this in the following way:
var scrollviewer = FindVisualChild<ScrollViewer>(treeview);
if (scrollviewer != null) scrollviewer.ScrollToEnd();
treeviewitem.BringIntoView();
Including FindVisualChild for completeness:
private static T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null) return correctlyTyped;
T descendent = FindVisualChild<T>(child);
if (descendent != null) return descendent;
}
}
return null;
}
Just to add, I do this without virtualization as I prefer to take the hit when I first load the treeview. I used this method on a treeview with over one thousand items without noticing any delay in the UI.

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.

Dynamic spacing between controls in the Toolbar in WPF

I have a toolbar which contains several controls, say 10.
I want to place last three controls in the right corner of the tool bar with some space between these three controls and remaining controls.
When I resize the window, it should reduce the space, once there is no space it should be cliped to overflow drop down one by one.
Note: I need to use only one toolbar.
I did this once before
The items existed in a customized ScrollViewer so they could go off-screen, and the SizeChanged event of the ScrollViewer would check each item to see if it was off-screen or not. If it wasn't visibile, or was partially visible, then the item would get hidden and the data object would get added to a Menu. If the item was visible, it would remove it from the Menu if it existed there, and set it visible. The Menu was only visible if it had items.
This was done a while back when I was still new to WPF, so I'm sure the process could be improved on.
The helper class I used to determine item visibility is listed below
public static ControlVisibility IsObjectVisibleInContainer(FrameworkElement child, UIElement parent)
{
GeneralTransform childTransform = child.TransformToAncestor(parent);
Rect childSize = childTransform.TransformBounds(new Rect(new Point(0, 0), new Point(child.Width, child.Height)));
Rect result = Rect.Intersect(new Rect(new Point(0, 0), parent.RenderSize), childSize);
if (result == Rect.Empty)
{
return ControlVisibility.Hidden;
}
else if (result.Height == childSize.Height && result.Width == childSize.Width)
{
return ControlVisibility.Full;
}
else if (result.Height == childSize.Height)
{
return ControlVisibility.FullHeightPartialWidth;
}
else if (result.Width == childSize.Width)
{
return ControlVisibility.FullWidthPartialHeight;
}
else
{
return ControlVisibility.Partial;
}
}
public enum ControlVisibility
{
Hidden,
Partial,
Full,
FullHeightPartialWidth,
FullWidthPartialHeight
}
It could be used like this:
ControlVisibility itemVisibility =
MyHelpers.IsObjectVisibleInContainer(someItem, parentContainer);

How to detect double click on list view scroll bar?

I have two list view on WPF. The first listview is loaded with a Datatable. When double clicking on one item from the first listview, the selectedItem is moved to the second listview.
The problem arises when appears an scroll bar in the first list view due to a lot of elements loaded from the DataTable. If a select one item and double click on the scroll bar down arrow, MouseDoubleClick event is launched and the selected item is moved to the second listview.
How I can detect the double click on the scroll bar to prevent this?
Thanks a lot!
I tested the above code which was very helpful, but found the following to be more stable, as sometimes the source gets reported as GridViewRowPresenter when in fact you are double clicking an item.
var src = VisualTreeHelper.GetParent((DependencyObject)e.OriginalSource);
var srcType = src.GetType();
if (srcType == typeof(ListViewItem) || srcType == typeof(GridViewRowPresenter))
{
// Your logic here
}
Try this in you MouseDoubleClick event on the first Listview:
DependencyObject src = VisualTreeHelper.GetParent((DependencyObject)e.OriginalSource);
if(src is Control && src.GetType() == typeof(ListViewItem))
{
// Your logic here
}
Based on this.
I am using this in various projects and it solves the problem you are facing.
private void ListBox_OnMouseDoubleClick(object pSender, MouseButtonEventArgs pE)
{
FrameworkElement originalSource = pE.OriginalSource as FrameworkElement;
FrameworkElement source = pE.Source as FrameworkElement;
if (originalSource.DataContext != source.DataContext)
{
logic here
}
}
When you have the DataContext you can easy see if the sender is an item or the main listbox
I've got the final solution:
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var originalSource = (DependencyObject)e.OriginalSource;
while ((originalSource != null) && !(originalSource is ListViewItem)) originalSource = VisualTreeHelper.GetParent(originalSource);
if (originalSource == null) return;
}
it works for me.

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