Scrolling a listbox with page up/down - wpf

I had an ItemsControl, which you could use page up/down buttons to scroll expectedly. I switched it with a ListBox, to apply (thru triggers) a new DataTemplate when an item is selected.
Everything works fine, until using pageup or pagedown buttons. Instead of scrolling one page, it scrolls till the end or the beginning.
My previous attemps:
Not changing the item height when DataTemplate changes
Removed IsSelected=True trigger completely
All SelectionMode values
Do I miss something trivial?

private void RaiseKeyDownUpEventsOnEntitiesBox(Key key)
{
KeyEventArgs keyEventArgs = new KeyEventArgs(
InputManager.Current.PrimaryKeyboardDevice,
Keyboard.PrimaryDevice.ActiveSource,
System.Environment.ProcessorCount, key);
keyEventArgs.RoutedEvent = UIElement.KeyDownEvent;
entitiesBox.RaiseEvent(keyEventArgs);
keyEventArgs.RoutedEvent = UIElement.KeyUpEvent;
entitiesBox.RaiseEvent(keyEventArgs);
}
Page Down
RaiseKeyDownUpEventsOnEntitiesBox(Key.Next);
Page Up
RaiseKeyDownUpEventsOnEntitiesBox(Key.Prior);

Indeed, I missed something trivial. I forgot to remove the ScrollViewer outside the ItemsControl. I guess, this creates a confusion, since ListBox has its own ScrollViewer.
This raised another question. Previous ScrollViewer were automaticcally scrolled down from code behind. Now, that I can't reach the ListBox's ScrollViewer, I can't call its LineDown method. Here is my workaround:
// Responses is the ItemsSource
Responses.Add(e);
// xResponses is the ListBox
var item = xResponses.ItemContainerGenerator.ContainerFromIndex(0);
ScrollBar.LineDownCommand.Execute(null, item as IInputElement);
In the beginning, item might evaluate to null, however this does not create a problem. After adding a few items, luckily before we need to scroll, a container is returned successfully. Note that index is not important here, all we need is an IInputElement inside the ScrollViewer.

Related

WPF ListBox Scrolling and Button Visibility

I am replacing the scroll bar for a list view with a "scroll up" and "scroll down" buttons. My question is, is there any way to show the buttons only when the list box can be scrolled?
i.e. My listbox may only have a couple of items...in that case I wouldn't need to show the buttons becuase there is nothing to scroll to.
I'm implmenting this across multiple listboxes and there is no set size of the items/lisboxes. I'm hoping there is some event that I can hook onto like a "scrollviewer_initializeed" or something.
Set the ListBox.ScrollView.VerticalScrollBarVisibility to Hidden and handle the ListBox.ScrollView.ScrollChanged event like this:
<ListBox ScrollViewer.ScrollChanged="ListBox_ScrollChanged"
ScrollViewer.VerticalScrollBarVisibility="Hidden" />
And then add this method to handle the ScrollChanged event:
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ButtonScrollUp.Visibility = ButtonScrollDown.Visibility =
((ScrollViewer)e.OriginalSource).ScrollableHeight > 0
? Visibility.Visible
: Visibility.Collapsed;
}
This is assuming your buttons are named ButtonScrollUp and ButtonScrollDown.
ScrollViewer.ScrollableHeight will equal the number of items out of view, so if it is greater than 0 your buttons should be visible.
The simplest solution would be to custom style the scrollviewer:
Simply set the Vertical scroll bar's visibility to "Auto", and hide all parts of the control template except for the top and bottom "RepeatButton" parts.
An example of styling the scrollViewer is here, but I'm sure you could find better ones with a quick search.

DataGrid + ContextMenu: How to get the row under the right click

In Silverlight 5, I have a DataGrid with a ContextMenuService.ContextMenu. If you click a row to select, then right click, you just check the grid.selecteditem for context. However, if you right click a row without selecting it, you don't have that row's context when the menu opens. How do you get the DataContext row of the grid that was right clicked on when the context menu opens? The right click on the grid seems to be an option, but it is intercepted for the contextmenu and does not fire unless a contextmenu is already open/in focus.
I have found tons of examples of getting around the original issue with Silverlight 4 and detecting the rown on right click. However, the contextmenu now intercepts the rigth click of the grid, so those no logner work. I also found posts on 'bugs' with the initial relase of the ContextMenu. All these posts/blogs are making it hard to find a current answer or solution.
yeah it seems like Silverlight 5 has changed something that breaks the old tricks.
We've been doing this: add row enters on row load handler. EG:
private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.MouseEnter += new MouseEventHandler(Row_MouseEnter);
e.Row.MouseLeave += new MouseEventHandler(Row_MouseLeave);
}
void Row_MouseEnter(object sender, MouseEventArgs e)
{
DataGridRow dgr = sender as DataGridRow;
IncidentGrid.SelectedItem = dgr.DataContext;
}
pretty ugly i know, but it's working.
Besides Roger's concerns of accuracy, I didn't want to wire up those events on every row because my grid is quite large, and I have to keep it lean.
I found a solution here...
https://mutelight.org/silverlight-datagrid-make-right-click-select-a-row
Apply just one event handler to the grid's MouseRightButtonDown event. The event args has the mouse position and you can use VisualTreeHelper to find which DataGridRow the mouse is over. Then that row's DataContext will have your SelectedItem.

Reset scrollbar on ItemsSource change

Whenever I change the ItemsSource (or it fires a collection reset), WPF doesn't reset the scrollbar position of ListView/DataGrid to the top. This is no big deal if there is a nice way of doing this manually, but it seems there isn't.
Currently I do:
ScrollViewer.ScrollToTop();
ScrollViewer.UpdateLayout();
Grid.ItemsSource = Data;
Which is causing some flicker because of UpdateLayout repainting the control first.
The other way is to do:
Grid.ItemsSource = Data;
ScrollViewer.ScrollToTop();
This doesn't seem to cause any flicker, but because the scroll position is changed AFTER ItemsSource is set, it causes WPF to first iterate over the wrong items in the ItemsSource, before it gets the right items (with offset 0). Because the collection is virtualized this causes needless SQL queries for items that will never be displayed.
Is there another way of doing this which doesn't flicker or retrieves the wrong items first?
Just attached this event on the control that will hold the collection:
Assuming that it is a listbox:
lstItems.SourceUpdated += new EventHandler<DataTransferEventArgs>(lstItems_SourceUpdated);
Then you will have a event that looks like this.
void lstItems_SourceUpdated(object sender, DataTransferEventArgs e)
{
ScrollViewerName.ScrollToTop();
}

WPF TabControl Switch behaviour

I have a tabcontrol which binds to an observable collection of tabs.
The tabcontrol always has the first tab which hosts a listview bound to another observable collection.
On selecting an item in that list view a new tab is created an focus given to it.
The problem I am having is:
When I switch back to the first tab there is a pause while it redraws / creates the listview items (contains images so slow)
The item selected before moving to the new tab is nolonger selected. Instead the listview is at the top with no item selected.
Can someone please explain to me how the tabcontrol operates is it really distroying the tab item content each time? and how I can instead have a behaviour where the item remains selected when I return to that tab?
Update:
I have confirmed by adding debug print messages to events that no events fire on this switch-back and forth but the first tab is being unloaded - more specifically the usercontrol hosted in that tab is??.
It sounds like the ObservableCollection is the culprit. If you are changing the collection items to control the display, then every time the collection changes won't it redraw the entire tab collection?
Instead, why not maintain the TabItem collection directly? You could then manage the Visibility property of the TabItems to display them or not.
First I needed to ensure my listview bound to my collection correctly i.e. the item stayed selected by adding the property:
IsSynchronizedWithCurrentItem="True"
I then added a loaded event handler to the listview so the item is scrolled into view on switching back:
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(DataContext);
if (collectionView != null)
{
ItemControl.ScrollIntoView(collectionView.CurrentItem);
}
}

Maintain scroll position on updating the ItemSource of a silverlight datagrid

I'm using a DataGrid in my silverlight application to display some data that's refreshed on a timer. My problem is that when this happens the vertical scrollbar in the grid resets to the top, whereas I want it to stay in the same position. Does anyone know how I can make this happen?
I've tried overriding the ItemsSource property on the grid to store the vertical scroll position and then reset it, but this only affects the scrollbar and doesn't force the correct rows to be displayed. Is there a way to force this behaviour?
Here is a similar question about Setting the scroll bar position on a ListBox
After rebinding Silverlight Listbox control how do you get it listbox to scroll to back to the top?
Since the DataGrid also supports a ScrollIntoView method, you should be able to use a similar technique such as
theDataGrid.ItemsSource = data;
theDataGrid.UpdateLayout();
theDataGrid.ScrollIntoView(theDataGrid.SelectedItem, theDataGrid.Columns[0]);
I couldn't find a decent answer last time I looked. I wanted to keep the current element selected in the grid but that wouldn't work on an ICollectionView refresh (I use MVVM and get automatic updates from the server).
ScrollIntoView() was not an option for me because the currently selected item may NOT be in view. Having the CurrentChanged event firing out of control was also quite a bother.
In the end, I used the Infragistics grid and it does just that out of the box. Problem solved for me.
You may have a look at the DevExpress free grid. I think it had the same nice behaviour (I tested it but I can't remember the outcome).
You could try setting the SelectedItem thro the UI thread, so that the UI can refresh itself,
like so
private void Button_Click(object sender, RoutedEventArgs e)
{
Person p = new Person() { Name="sss",Age=11}; //datagird's itemsSource is Collection<person>
people.Add(p);
dg.SelectedItem = p; //dg is my datagrid name
Dispatcher.BeginInvoke(() => { dg.SelectedItem = p; });
}
Im assuming that new rows are loaded thro the ViewModel, so thats why it makes sense to place the BeginInvoke there. Since the ViewModel operations run on a different thread, and just setting the SelectedItem on its own might not work, this has worked for someone else
I've also had issues with this. I solved it by remembering the item I want to scroll to, then re-binding the DataGrid. I handle the LayoutUpdated event in order to implement the desired functionality:
void MyDataGrid_LayoutUpdated(object sender, EventArgs e)
{
// Reference the data item in the list you want to scroll to.
object dataItem = yourDataItem;
// Make sure the item is not null and didn't already scroll to the item.
if (dataItem != null && this.dataItemScrolledTo != dataItem)
{
// Remember the item scrolled to.
this.dataItemScrolledTo = dataItem;
// Scroll datagrid to the desired item.
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
}
}
I've modified CodeMaster's solution so that you don't need a class level variable. Put this code in the method that updates the ItemsSource. It will dynamically create the eventhandler, attach it, then detach it.
EventHandler MyDataGrid_LayoutUpdated = null;
MyDataGrid_LayoutUpdated = (s, e) =>
{
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
MyDataGrid.LayoutUpdated -= MyDataGrid_LayoutUpdated;
};
MyDataGrid.LayoutUpdated += MyDataGrid_LayoutUpdated;

Resources