简体   繁体   中英

how to focus on the keyboard on a textbox in WPF listview after item.refresh

I have a WPF window with a listview with textboxes in it, and I want to enable users to switch between textboxes with the TAB key. I created a function that does that, but every time the textbox loses focus, I refresh the listview and so the listview itself gets focused.

I know the problem occurs because of the refresh event because commenting that part will result in the correct element (the textbox) being focused. I have tried many alternative solutions but none works ( prevent item losing focus when refreshing list view in c# ; Focus on TextBox after ListView 'SelectionChanged' Event ).

It seems there is something problematic in focusing on an that element after the listview is refreshed.

I have tried remembering the index of the item and then focusing on it, after the TAB key was pressed. tried also remembering the focused control and then focusing on it after the refresh.

    private void RTB_Reference_LostFocus(object sender, RoutedEventArgs e)
    {
        int Index = DetailsList.SelectedIndex;
        Index = DetailsList.Items.IndexOf(DetailsList.SelectedItem);

        try
        {
            //Get cell value by using sender Object
            string inTime = ((System.Windows.Controls.TextBox)sender).Text;

            DetailItem item = (DetailItem)DetailsList.Items[Index];
            item.Reference = inTime;

            UpdateExplanation(item);

        }

        catch (Exception)
        {

        }
    }

    private void RTB_Detail_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Tab)
        {
            e.Handled = true;
            //System.Windows.MessageBox.Show("Tab");
            int Idx = DetailsList.SelectedIndex;
            System.Windows.Controls.ListViewItem lvi = (System.Windows.Controls.ListViewItem)DetailsList.ItemContainerGenerator.ContainerFromItem(DetailsList.SelectedItem);
            GUF.FocusItem(DetailsList, Idx, "RTB_Detail");
            //IsLastKeyTAB = true;
        }
        //else
        //    IsLastKeyTAB = false;

    }

    private void UpdateExplanation(DetailItem item)
    {

        item.Explanation = GetExplanation(item.Reference, item.Detail);

        IInputElement focusedControl = Keyboard.FocusedElement;
        DetailsList.Items.Refresh();
        focusedControl.Focus();

        RefreshDetailsList(DetailsList, IsEnglish);


    }

expected result is to keep focus on that textbox after the refresh. that does not occur...

EDIT 1

This is the listview xaml:

    <ListView FlowDirection="RightToLeft" x:Name="DetailsList" VirtualizingStackPanel.IsVirtualizing="False" 
        HorizontalAlignment="Stretch" VerticalAlignment="Top"
        HorizontalContentAlignment="Center" ScrollViewer.VerticalScrollBarVisibility="Visible" Padding="0 0 0 25"
        AllowDrop="True"
        ItemsSource="{Binding DetailItem}"
        Loaded="ListView_Loaded"
        Margin="26,157,23,0"
        dd:DragDrop.IsDragSource="True"
        dd:DragDrop.IsDropTarget="True"
        dd:DragDrop.DropHandler="{Binding}" Height="599">


        <ListView.ItemContainerStyle>
            <Style TargetType="{x:Type ListViewItem}">
                <Setter Property="BorderThickness" Value="0" />
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Margin" Value="4, 4, 4, 4"/>
                <Setter Property="FontWeight" Value="DemiBold"/>
                <Setter Property="Height" Value="22"/>
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListViewItem}">
                            <Border BorderBrush="Transparent" BorderThickness="0" Background="{TemplateBinding Background}">

                                <GridViewRowPresenter HorizontalAlignment="Stretch" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Width="Auto" Margin="0" Content="{TemplateBinding Content}"/>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>

                <Style.Triggers>

                    <Trigger Property="IsMouseOver" Value="True">
                        <!--   <Setter Property="Background" Value="#6B54FF"/> -->
                        <Setter Property="Foreground" Value="#6B57FF"/>
                        <Setter Property="BorderThickness" Value="2" />
                    </Trigger>

                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="BorderThickness" Value="2" />
                        <Setter Property="BorderBrush" Value="#6B57FF"/>
                        <Setter Property="Foreground" Value="#6B57FF" />
                    </Trigger>
                </Style.Triggers>

            </Style>
        </ListView.ItemContainerStyle>




        <ListView.View>
            <GridView>

                <GridViewColumn Width="50" Header="סימון" DisplayMemberBinding="{Binding Mark}"/>

                <!--
                                <GridViewColumn Width="30" >
                                    <GridViewColumn.CellTemplate>
                                        <DataTemplate >
                                            <Button Style="{StaticResource PlusButtonStyle}" x:Name="buttonPlusDocument" Click="buttonPlusDocument_Click"  />
                                        </DataTemplate>
                                    </GridViewColumn.CellTemplate>
                                </GridViewColumn>
                                -->
                <GridViewColumn Header="הפניה במסמך" Width="150">
                    <GridViewColumn.CellTemplate>

                        <DataTemplate>

                            <TextBox x:Name="RTB_Reference" BorderBrush="#5f27ff" BorderThickness="1" KeyDown="RTB_Reference_KeyDown" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True" 
                                LostFocus="RTB_Reference_LostFocus" GotFocus="RTB_Reference_GotFocus">

                                <TextBox.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="2"/>
                                        <Setter Property="BorderBrush" Value="#5f27ff"/>
                                        <Setter Property="BorderThickness" Value="1" />
                                    </Style>
                                </TextBox.Resources>
                            </TextBox>
                            <!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->

                        </DataTemplate>

                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

                <GridViewColumn Header="פרט" Width="150">
                    <GridViewColumn.CellTemplate>

                        <DataTemplate>

                            <TextBox x:Name="RTB_Detail" BorderBrush="#5f27ff" BorderThickness="1" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True" 
                                KeyDown="RTB_Detail_KeyDown" LostFocus="RTB_Detail_LostFocus" GotFocus="RTB_Detail_GotFocus">

                                <TextBox.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="2"/>
                                        <Setter Property="BorderBrush" Value="#5f27ff"/>
                                        <Setter Property="BorderThickness" Value="1" />
                                    </Style>
                                </TextBox.Resources>
                            </TextBox>
                            <!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->

                        </DataTemplate>

                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

                <GridViewColumn Header="הסבר" Width="350"  DisplayMemberBinding="{Binding Explanation}"/>



                <GridViewColumn Width="30" >
                    <GridViewColumn.CellTemplate>

                        <DataTemplate >

                            <Button Style="{StaticResource DeleteButtonStyle}" x:Name="BT_DeleteDetail" Click="BT_DeleteDetail_Click"  />

                        </DataTemplate>

                    </GridViewColumn.CellTemplate>
                </GridViewColumn>


            </GridView>
        </ListView.View>
    </ListView>

This is the details class:

    public class DetailItem
    {
        public string Mark { get; set; }
        public string Reference { get; set; }
        public string Detail { get; set; }
        public string Explanation { get; set; }
    }

I refresh the listview so that the Explanation text will be updated.

I don't went through your whole code but only those parts that are relevant to your question. To simplify things, I will use the attached property TabNavigation and the data binding feature. The binding will automatically update the control values so that the refreshing of the ListView and the focus handling becomes redundant.

Remark: To shorten the XAML code I will only show the relevant code portions.

Tab navigation

Set the attached property KeyboardNavigation.TabNavigation on the ListView to Continue so that the focus will loop through the list:

  <ListView x:Name="DetailsList"
            KeyboardNavigation.TabNavigation="Continue"
            ...
  </ListView>

You can control which elements can receive focus on Tab key pressed by setting the property IsTabStop on the elements. The default value is True . So set it to False on those elements you wish to exclude. It's useful to disable Tab focus on the ListViewItem itself so that the Tab key is only effective for the containing controls:

    <ListView.ItemContainerStyle>
      <Style TargetType="{x:Type ListViewItem}">
        <Setter Property="IsTabStop"
                Value="False" />
        ...
      </Style>
    </ListView.ItemContainerStyle>

If you wish to exclude the Button as well, so that you only jump between the TextBox elements, set the Button 's IsTabStop property to False as well:

        <GridViewColumn Width="30">
          <GridViewColumn.CellTemplate>
            <DataTemplate>
              <Button x:Name="BT_DeleteDetail"
                      IsTabStop="False" 
                      ...
              />
              ...
            </DataTemplate>
          </GridViewColumn.CellTemplate>
        </GridViewColumn>

If you also wish to change the order the elements will receive focus on Tab key pressed, you can do it by setting the element's TabIndex property. A control with a lower tab index receives focus before a control with a higher index.

Binding the TextBox

To enable binding to a data model it is required that the data model (view model) implements INotifyPropertxChanged :

public class DetailItem : INotifyPropertyChanged
{
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private string mark;
  public string Mark
  {
    get => this.mark;
    set
    {
      if (value == this.mark) return;
      this.mark = value;
      OnPropertyChanged();
    }
  }

  private string reference;
  public string Reference
  {
    get => this.reference;
    set
    {
      if (value == this.reference) return;
      this.reference = value;
      OnPropertyChanged();
    }
  }

  private string detail;
  public string Detail
  {
    get => this.detail;
    set
    {
      if (value == this.detail) return;
      this.detail = value;
      OnPropertyChanged();
    }
  }

  private string explanation;
  public string Explanation
  {
    get => this.explanation;
    set
    {
      if (value == this.explanation) return;
      this.explanation = value;
      OnPropertyChanged();
    }
  }
}

In order access the view model from your DataTemplate set its DataType property to the type of the view model ( DetailItem ). Remember to always set the DataType of a DataTemplate . Then add the Binding to the TextBox . Note that the Mode (direction) of the binding is set to OneWayToSource . This limits the binding to set data from TextBox to DetailItem only:

          <GridViewColumn.CellTemplate>
            <DataTemplate DataType="viewModels:DetailItem">
              <TextBox x:Name="RTB_Reference"
                       Text="{Binding Reference, Mode=OneWayToSource}"
                       ... 
              </TextBox>
              ...
            </DataTemplate>
          </GridViewColumn.CellTemplate>

          <GridViewColumn.CellTemplate>
            <DataTemplate DataType="viewModels:DetailItem">
              <TextBox x:Name="RTB_Detail"
                       Text="{Binding Detail, Mode=OneWayToSource}"
                       ... 
              </TextBox>
              ...
            </DataTemplate>
          </GridViewColumn.CellTemplate>

The final step is to update the DetailItem.Explanation property. To do this, we move the UpdateExplanation() method into the view model DetailItem . Since we use binding we can now get rid of all the ListView refresh and focus logic inside, so the method becomes smaller. Note that since we moved the method into the DetailItem class, we can also remove the parameter of the method:

private void UpdateExplanation()
{
  this.Explanation = GetExplanation(this.Reference, this.Detail);
}

The UpdateExplanation() method is invoked directly from the setter of the Reference and Detail property, so that everytime these properties are changed the Explanation value will be updated. With the updated Reference and Detail setter the final version of the DetailItem class will then look as followed:

public class DetailItem : INotifyPropertyChanged
{
    private void UpdateExplanation()
    {
      this.Explanation = GetExplanation(this.Reference, this.Detail);
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private string mark;
    public string Mark
    {
      get => this.mark;
      set
      {
        if (value == this.mark) return;
        this.mark = value;
        OnPropertyChanged();
      }
    }

    private string reference;
    public string Reference
    {
      get => this.reference;
      set
      {
        if (value == this.reference) return;
        this.reference = value;
        OnPropertyChanged();
        UpdateExplanation();
      }
    }

    private string detail;
    public string Detail
    {
      get => this.detail;
      set
      {
        if (value == this.detail) return;
        this.detail = value;
        OnPropertyChanged();
        UpdateExplanation();
      }
    }

    private string explanation;
    public string Explanation
    {
      get => this.explanation;
      set
      {
        if (value == this.explanation) return;
        this.explanation = value;
        OnPropertyChanged();
      }
    }
  }

Try Below Code. Enjoy !!!

<Grid>
    <ListView Margin="45.5,47.5,41.167,0" Name="lvtestListview"   Height="188.333" VerticalAlignment="Top">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                <Setter Property="VerticalContentAlignment"  Value="Stretch"/>
                <!--<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>-->

                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="true" >
                        <Setter Property="Foreground" Value="Blue" />
                        <Setter Property="Background" Value="LightGreen" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Item Code" Width="100">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Border BorderBrush="Gray"  BorderThickness=".5" Margin="-6,-3">
                                <TextBlock Text="{Binding ItemCode}"  Margin="6,3"/>
                            </Border>

                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>             
                <GridViewColumn Header="Item Name" Width="100">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
                                <TextBox Loaded="txtBarcode_Loaded"  Text="{Binding ItemName}" Name="txtBarcode" Focusable="{Binding IsFocused}"      Cursor="IBeam" />
                            </Border>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>             
                <GridViewColumn Header="Next Item" Width="100">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
                                <TextBox TabIndex="3" Name="txtgvPL1" KeyDown="txtgvPL1_KeyDown" Text="{Binding PL1}"  Cursor="IBeam"/>

                            </Border>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>

</Grid>

code behind

public class ClassA : INotifyPropertyChanged {

    public string ItemCode { get; set; }
    public string ItemName { get; set; }
    

    private bool isFocused;


    public event PropertyChangedEventHandler PropertyChanged;



  


    public bool IsFocused
    {

        get
        {
            return isFocused;
        }

        set
        {
            if (value != isFocused)
            {
                isFocused = value;
                RaisePropertyChanged("IsFocused");

            }
        }


    }



    private void RaisePropertyChanged(string propName)
    {
      

        PropertyChangedEventHandler eh = PropertyChanged;
        if (eh != null)
        {
            eh(this, new PropertyChangedEventArgs(propName));
        }
    }



}




public partial class MainWindow : Window
{

    List<ClassA> lstClassA = new List<ClassA>();
    public MainWindow()
    {
        InitializeComponent();
        BindGrid();
    }

    private void BindGrid()
    {
       

        ClassA ca = new ClassA();
        ca.ItemCode = "A";
       
        ca.IsFocused = true;

        lstClassA.Add(ca);


        lvtestListview.ItemsSource = lstClassA;


     }

    private void txtBarcode_Loaded(object sender, RoutedEventArgs e)
    {
        Keyboard.Focus(((TextBox)sender));
    }

    private void txtgvPL1_KeyDown(object sender, KeyEventArgs e)
    {

        if (e.Key == Key.Enter  )
        {
            ClassA ca = new ClassA();
            ca.ItemCode = "B";
            ca.IsFocused = true;

            lstClassA.Add(ca);


            foreach (ClassA a in lstClassA)
            {
                if (a.ItemCode == "A")
                {
                    a.IsFocused = false;
                }

            }


            lvtestListview.ItemsSource = lstClassA;
            lvtestListview.Items.Refresh(); 

        }

      

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM