WPF tab index issue - wpf

I'm working on an application where a user defines a the controls on a form and can set the tab index of any control. As each control is added to the Grid that comprises the viewable form area, the tab index is set to either 0 (default) or some user-defined tab index. Tabbing through the form works fine until the tabindex of one of the controls is changed at runtime(the index doesn't seem to matter.) After this, tabbing cycles only through some of the controls and in addition, the window menu items are now tab stops(they weren't prior to the tabindex change.) Also, the menu's tab properties aren't bound to any datacontext.
The control that's currently changed is a checkbox, but I'm unable to reproduce the behavior with a simplified layout, so any suggestions would help.

Our "form pages" user controls invisible and beneath the current visible page were never disabled when new ones were pushed on the top. Therefore they were included in the tab indexing behavior causing unwanted side effects.
This helped me get to the bottom of the issue:
void InitializeFocusLogger() {
//debug key logging to make focus bugs easier to track
EventManager.RegisterClassHandler(
typeof(UIElement),
Keyboard.PreviewGotKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
}
string lastID = string.Empty;
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
FrameworkElement control = e.NewFocus as FrameworkElement;
if (control == null) return;
ControlViewModel controlVM = control.DataContext as ControlViewModel;
if (controlVM == null || lastID == controlVM.ID) return;
lastID = controlVM.ID;
Debug.Print("Focused: {0} TabIndex: {1}", controlVM.ID, controlVM.TabIndex);
}

Related

TabControl losing selected tab on Window.Show

We have an MVVM (Cinch) solution that has a Window with a TabControl in it. The ItemsSource is bound to a CollectionView (DefaultView generated from an ObservableCollection), with IsSynchronizedWithCurrentItem=true. Everything works great the first time the Window loads... tabs are displaying correctly and the user can switch between them.
The problem occurs when the Window is hidden and shown again. The first tab is always selected again, regardless of what the CurrentItem in the CollectionView is. The line before the .Show has the CurrentItem as the tab we want, but the tab switches during the .Show operation.
Has anyone else run into this issue?
This is definitely a hack. But you could override the Activated event and store the tab index before it gets reset and set it again when it reactivates.
protected override void OnActivated(EventArgs e)
{
int tabControlIndex = myTabController.SelectedIndex;
base.OnActivated(e);
myTabController.SelectedIndex = tabControlIndex ;
}

WPF DataGrid ItemSource Refresh - GridColumns shows strange behaviour

I am developing WPF UserControl based on WPF DataGrid, to support dynamic column generation with our own business based context menu.
I've created Dependency Property called DataSource, when I set DataSource calling a custom method to Bind my dataSource to Create columns on the fly and set ItemSource property. All works fine the first time. I have a context menu called Refresh, while the user clicks Refresh the SQL will execute and the same cycle of the above-mentioned actions will happen. During the second time, the rows and columns are created perfectly. But when I do Horizontal scroll the Column headers are NOT showing properly, it loses their visual state while scrolling.
My Custom Property - DataSource
public static DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(GridDataModel), typeof(MyGridView),
new PropertyMetadata((dependencyObject, eventArgs) =>
{
if (eventArgs.OldValue != null)
{
((GridDataModel)eventArgs.OldValue).Dispose();
}
BindToDataSource((MyGridView)dependencyObject, (GridDataModel)eventArgs.NewValue);
}));
My Custom method which is calling everytime I set DataSource property:
private static void BindToDataSource(MyGridView view, GridDataModel dataModel)
{
if (view.ViewModel != null)
{
BindingOperations.ClearAllBindings(view.GridView);
view.GridView.Items.Clear();
view.GridView.Columns.Clear();
view.GridView.ItemsSource = null;
view.ViewModel.Dispose();
}
view.ViewModel = new MyGridViewModel(dataModel);
view.ViewModel.PrepareGridView();
view.LayoutRoot.DataContext = view.ViewModel;
view.CreateColumns();
view.GridView.SetBinding(DataGrid.ItemsSourceProperty, new Binding("DisplayRows"));
}
The Below code I used to call on Refresh Menu Click:
private void OnRefreshClick(object sender, RoutedEventArgs e)
{
var data = new TestDataAccess();
DataSource = data.MakeGridModel("select Top 200 * from ApplicationUSer"); //Assigning DataSource Again, which will call the above method.
GridView.UpdateLayout();
}
After refresh, you could see the column alignment goes strange when doing the horizontal scroll.
Tried using GridColumnWidth =0, and setting again to Auto, Tried GridView.UpdateLayout().
I solved the above problem my self.
Instead of BindingOperations.ClearAllBindings() i used BindingOperations.ClearBinding(view.GridView, DataGrid.ItemSourceProperty) - which cleared out only ItemSource so that i can regain memory by Items.Clear() for every time i bind the data.
Due to ClearAllBindings, its clears headers panel bindings also, so its looses ParentTemplate.Width property, because of that strange problem happend during horizontal scroll.

WPF drag and drop from a ListBox that has SelectionMode=Extended

I have a ListBox and want the selection-mode to be extended. Also I want have to implement drag and drop functionality. The problem now is, that if the mouse is clicked on a selected item, it will be immediately be selected as single selection instead of waiting to the mouse-up-event for doing this.
Due to this behaviour, start dragging multiple items is for the user quasi impossible because always he clicks on the selection to start dragging, the selection changes to the item that is under the mouse and starts the drag-operation with this item.
Is there a good workaround for this problem or does even exists an official solution?
Here's what I've done. In your DragDrop code, subscribe to the PreviewMouseLeftButtonDown. If the item you are already clicking on is selected, then set e.Handled to true.
In my sample below, I identify a part of the list box item as a drag grip (with bumps) so that I can distinguish between the item and a drag surface. I just had to get the list box item data template and the drag and drop behavior to agree on a name of the drag grip element.
The PreviewMouseLeftButtonDown from my work in progress:
private void ItemsControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
ItemsControl itemsControl = this.AssociatedObject as ItemsControl;
if (itemsControl != null)
{
this.sourceItemContainer = itemsControl.ContainerFromElement((Visual)e.OriginalSource) as FrameworkElement;
}
// If this is an multiple or extended selection list box, and on a drag grip, then ensure the item being hit is selected
// This prevents the ItemsControl from using this MouseDown to change selection, except over a selected item's drag grip.
if ((this.IsMultipleSelectionListBox() == true) && (this.IsOriginalSourceDragGrip(e) != false) && (this.IsSourceListBoxItemSelected() == true))
{
e.Handled = true;
}
}
The easiest workaround i can think of would be to change the ListBoxItem to select on MouseUp not Down like so and change the ContainerGenerator to serve your custom ListBoxItems:
public class CustomListBoxItem : ListBoxItem
{
protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
{
//do nothing
}
protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
{
base.OnMouseLeftButtonDown( e );
}
}
You might need some MouseLeave/LeftButtonDown logic if you want to prevent different items selecting when moving through the List while holding the mouse button down.
Use PreviewMouseLeftButtonDown to add the selected items for the drag operation.

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;

select tabItem programmatically in WPF

I have different tabItems in a TabControl
and each tabItem has some input fields.
I am moving between the tabItems programmatically (like a wizard to move from the first to the next)
I am using this code inside the "Next" button
tabItem2.isSelected = true;
my problem that when I move between the tabItems by clicking on them, the focus (keyboard focus) will move to the first textbox input.
But programmatically with the previous code, the focus won't move to the first input textbox item inside the tabItem.
Any idea?
If you're forcing the IsSelected property, I'd also give the first TextBox a name and set the focus after you set the selected tab.
If you're building your UI dynamically, this won't work, but you can create a utility method which searches the logical tree (or the visual tree if you're using presenters/view-models) for the first input control and then set the focus.
These solutions didn't work for me. It got as far selecting the TabItem I wanted, but it wasn't able to select/focus the desired TreeViewItem. (It would only focus the TVI if the TabItem was already selected.) The solution below finally worked for me.
(FYI: The snippets below are part of app that is similar to Microsoft Help Viewer 2.0. When you click the "Sync" button, it first selects the Contents tab if not already selected, then traverses into tree view until it finds the matching tree view item. Which it then selects/focuses.)
Cheers
private void OnClick_SyncContents(object sender, RoutedEventArgs e)
{
// If the help-contents control isn't visible (ie., some other tab is currently selected),
// then use our common extension method to make it visible within the tab control. Once
// it visible, the extension method will call the event handler passed (which is this method)
if (!this.m_UcHelpFileContents.IsVisible)
{
this.m_UcHelpFileContents.
SelectParentTabItem_WaitForMeToBecomeVisible_ThenCallThisEventHandlerWithNullArguments
(this.OnClick_SyncContents);
}
else
{
// Else the help-contents control is currently visible, thus focus the
// matching tree view item
/* Your code here that focuses the desired tree view item */
}
}
public static class CommonExtensionMethods
{
public static void
SelectParentTabItem_WaitForMeToBecomeVisible_ThenCallThisEventHandlerWithNullArguments
(this FrameworkElement frameworkElement, RoutedEventHandler eventHandlerToCallWhenVisible)
{
// First, define the handler code for when the given framework element becomes visible
DependencyPropertyChangedEventHandler HANDLER = null;
HANDLER = (s, e) =>
{
// If here, the given framework element is now visible and its tab item currently selected
// Critical: first and foremost, undo the latch to is-visible changed
frameworkElement.IsVisibleChanged -= HANDLER;
// Now invoke the event handler that the caller wanted to invoke once visible
frameworkElement.Dispatcher.BeginInvoke(eventHandlerToCallWhenVisible, null, null);
};
// Use our common extension method to find the framework element's parent tab item
TabItem parentTabItem = frameworkElement.GetFirstParentOfType<TabItem>();
if (parentTabItem != null)
{
// Assign the handler to the given framework element's is-visible-changed event
frameworkElement.IsVisibleChanged += HANDLER;
// Now set the tab item's is-selected property to true (which invokes the above
// handler once visible)
parentTabItem.IsSelected = true;
}
}
public static T GetFirstParentOfType<T>
(this FrameworkElement frameworkElement) where T : FrameworkElement
{
for (FrameworkElement fe = frameworkElement.Parent as FrameworkElement;
fe != null;
fe = fe.Parent as FrameworkElement)
{
if (fe is T)
return fe as T;
}
// If here, no match
return null;
}
}

Resources