Lutosław
Lutosław

Reputation: 666

WPF: Move focus to the next item in an ItemsControl on enter

WPF: When user presses the enter key while inside a texbox in a ItemsControl, I want to move focus to a textbox in the next item in ItemsControl, or create a new one if user was in the last item.

To be more clear:

SCENARIO 1

ItemsControl items:
[ textbox in item 1 ] <- user is here
[ textbox in item 2 ]
[ textbox in item 3 ]

After pressing Enter:

[ textbox in item 1 ]
[ textbox in item 2 ] <- user is here
[ textbox in item 3 ]

SCENARIO 2

ItemsControl items:

[ textbox in item 1 ]
[ textbox in item 2 ]
[ textbox in item 3 ] <- user is here

After pressing Enter:

[ textbox in item 1 ]
[ textbox in item 2 ]
[ textbox in item 3 ]
[ textbox in item 4 ] <- user is here

If it helps, here is the code for item data template:

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Grid Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="32"/>
            </Grid.ColumnDefinitions>
            <TextBox Text="{Binding Path=PartName, FallbackValue='----',TargetNullValue='----', NotifyOnSourceUpdated=True}" KeyDown="TextBox_KeyDown"/>
            <Button Grid.Column="1" FontSize="10" x:Name="DeletePartButton" Click="DeletePartButton_Click" Height="22">Usuń</Button>
        </Grid>
    </DataTemplate>
</ItemsControl.ItemTemplate>

EDIT 2: I use ItemsControl because selecting feature is not wanted.

EDIT 3: I have found a partial solution. It works for moving a focus to a next element, but not a new one (which is the most important functionality here)

    private void PartNameTextBox_KeyDown(object sender, KeyEventArgs e)
    {
        var box = (TextBox)sender;

        if (e.Key == Key.Enter)
        {
            var part = (PiecePart)box.DataContext;
            int index = part.ParentPiece.Parts.IndexOf(part);
            if (index == part.ParentPiece.PartCount - 1)
            {
                part.ParentPiece.Parts.Add(new PiecePart(GetNewPartName(part.ParentPiece)));
                bool success = PartListBox.ApplyTemplate();
                // try to force wpf to build a visual tree for the new item success = false :(
            }
// throws out of bounds exception if a new item was added (and wasn't added to a visual tree)
            var el = ((UIElement)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(PartListBox, 0),0),1),0),0),++index),0),0));
            el.Focus();
        }
    }

Upvotes: 5

Views: 6200

Answers (6)

Eric Eggers
Eric Eggers

Reputation: 84

ar.gorgin's solution works with some slight changes, mainly in recognition of the fact that the OP needs this to work on an ItemsControl instead of a ListBox:

if (e.Key == Key.Enter)
{
    var txt = sender as TextBox;
    var selecteditem = FindParent<ContentPresenter>(txt);
    int index = myItemsCtl.ItemContainerGenerator.IndexFromContainer(selecteditem);

    if (index < myItemsCtl.Items.Count)
    {
        var afterItem = myItemsCtl.ItemContainerGenerator.ContainerFromIndex(index + 1) as Visual;
        TextBox tbFind = GetDescendantByType(afterItem, typeof(TextBox), "txtBoxName") as TextBox;
        if (tbFind != null)
        {
            FocusHelper.Focus(tbFind);
        }
    }
}

Upvotes: 0

ar.gorgin
ar.gorgin

Reputation: 5002

On PreviewKeyDown of TextBox

private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
 if (e.Key == Key.Enter)
  {
   var txt= sender as TextBox;
   var selecteditem=FindParent<ListBoxItem>(txt);
   int index = ListBox.ItemContainerGenerator.IndexFromContainer(selecteditem);
   if(index<ListBox.Items.Count)
    {
    var afterItem=(ListBoxItem)ListBox.ItemContainerGenerator.ContainerFromIndex(index+1);
    TextBox tbFind = GetDescendantByType(afterItem, typeof (TextBox), "TextBox") as TextBox;
    if (tbFind != null)
    {
     FocusHelper.Focus(tbFind);
    }
   }
  }
}

public static Visual GetDescendantByType(Visual element, Type type, string name)
{
 if (element == null) return null;
 if (element.GetType() == type)
 {
  FrameworkElement fe = element as FrameworkElement;
  if (fe != null)
  {
     if (fe.Name == name)
     {
        return fe;
     }
  }
 }
Visual foundElement = null;
if (element is FrameworkElement)
  (element as FrameworkElement).ApplyTemplate();
for (int i = 0;
    i < VisualTreeHelper.GetChildrenCount(element);
    i++)
{
  Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
  foundElement = GetDescendantByType(visual, type, name);
  if (foundElement != null)
     break;
}
return foundElement;
}

public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
 //get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);

//we've reached the end of the tree
if (parentObject == null) return null;

//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
    return parent;
else
    return FindParent<T>(parentObject);
}

One more helper to set the Focus on the TextBox:

public static class FocusHelper
{
public static void Focus(UIElement element)
{
 element.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(delegate()
 {
    element.Focus();
 }));
}
}

Upvotes: 4

Lutosław
Lutosław

Reputation: 666

I got it. To move a focus to a next element, I use Sheridan's solution. To move a focus to a new element, I use an "adding" flag and TextBox.Loaded event.

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Grid Background="White" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="32"/>
            </Grid.ColumnDefinitions>
            <TextBox x:Name="PartNameTextbox" Text="{Binding Path=PartName, FallbackValue='----',TargetNullValue='----', NotifyOnSourceUpdated=True}" KeyDown="PartNameTextBox_KeyDown" 
         Loaded="PartNameTextbox_Loaded"/>
            <Button Grid.Column="1" FontSize="10" x:Name="DeletePartButton" Click="DeletePartButton_Click" Height="22">Usuń</Button>
        </Grid>
    </DataTemplate>
</ItemsControl.ItemTemplate>

code-behind:

bool partAdding = false;
private void PartNameTextBox_KeyDown(object sender, KeyEventArgs e)
{
    var box = (TextBox)sender;

    if (e.Key == Key.Enter)
    {
        var part = (PiecePart)box.DataContext;
        int index = part.ParentPiece.Parts.IndexOf(part);
        if (index == part.ParentPiece.PartCount - 1)
        {
            part.ParentPiece.Parts.Add(new PiecePart(GetNewPartName(part.ParentPiece)));
            UpdateCurrentLine(part.ParentPiece);
            partAdding = true;
        }
        // Gets the element with keyboard focus.
        UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;

        // Creating a FocusNavigationDirection object and setting it to a 
        // local field that contains the direction selected.
        FocusNavigationDirection focusDirection = FocusNavigationDirection.Down;

        // MoveFocus takes a TraveralReqest as its argument.
        TraversalRequest request = new TraversalRequest(focusDirection);

        // Change keyboard focus. 
        if (elementWithFocus != null)
        {
            elementWithFocus.MoveFocus(request);
        }
    }
}

private void PartNameTextbox_Loaded(object sender, RoutedEventArgs e)
{
    if (partAdding)
    {
        var box = ((TextBox)sender);            
        var pp = ((PiecePart) box.DataContext);
        if (pp.IsLastPart)
        {
            box.Focus();
            box.SelectionStart = box.Text.Length;
            partAdding = false;
        }
    }
}

Upvotes: 1

Sheridan
Sheridan

Reputation: 69979

The correct way to move focus to the next element in WPF is to use the TraversalRequest class which Represents a request to move focus to another control and the FocusNavigationDirection Enumeration which Specifies the direction within a user interface (UI) in which a desired focus change request is attempted. This example is taken from the TraversalRequest class page on MSDN:

// Creating a FocusNavigationDirection object and setting it to a 
// local field that contains the direction selected.
FocusNavigationDirection focusDirection = _focusMoveValue;

// MoveFocus takes a TraveralReqest as its argument.
TraversalRequest request = new TraversalRequest(focusDirection);

// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;

// Change keyboard focus. 
if (elementWithFocus != null)
{
    elementWithFocus.MoveFocus(request);
} 

Upvotes: 2

Nguyen Kien
Nguyen Kien

Reputation: 1927

Listbox.SelectedIndex = 0;

private void Listbox_OnKeyUp(object sender, KeyEventArgs e)
{
    if (e.Key== Key.Enter)
    {
         if(Listbox.Items.Count-1>Listbox.SelectedIndex)
             Listbox.SelectedIndex++;
         else 
             Listbox.SelectedIndex=0;
    }
}

This work when user focus on your listbox.

Upvotes: 1

Harshana Narangoda
Harshana Narangoda

Reputation: 783

C#

    private void ListBox_KeyUp(object sender, KeyEventArgs e)
    {
        int selectIndex = listBox.SelectedIndex;
        int listItemCount = listBox.Items.Count;
        if (e.Key == Key.Enter)
        {
            if (selectIndex == listItemCount - 1)
            {
                ListBoxItem newItem = new ListBoxItem();
                newItem.Content = "Your Content";
                listBox.Items.Add(newItem);
                listBox.SelectedItem = newItem;
            }
            else
            {
                listBox.SelectedIndex = selectIndex + 1;
            }
        }
    }

XAML

        <ListBox HorizontalAlignment="Left" Name="listBox" Height="92" KeyUp="ListBox_KeyUp">
        <ListBoxItem Content="First"/>
        <ListBoxItem Content="Second"/>
        <ListBoxItem Content="Third"/>
        <ListBoxItem Content="Fourth"/>
    </ListBox>

First I get the listbox item count as listItemCount, and also the selected item index as selectIndex, then check the key event is an Enter then check if we reached to the last of the list. if it is make a new list box item and added it to the list.

Upvotes: 0

Related Questions