Set Item Focus in ListView WPF
There are two types of focus in WPF - Keyboard Focus and Logical Focus. This link can give you more information about focus in WPF.
You can either do this:
ListViewItem item = myListView.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
item.Focus();
It's also possible to call
Keyboard.Focus(item);
If you also want to scroll the ListView
to the item's position, add this:
myListView.ScrollIntoView(item);
IMPORTANT NOTE: For this to work, you will need to set VirtualizingStackPanel.IsVirtualizing="False"
on your ListView
, which may cause it to perform slower. The reason this attached property is required is that when the ListView
is virtualized (which it is by default), the ListViewItems
aren't created for items that aren't displayed on the screen, which will cause ContainerFromIndex()
to return null
.
WPF ListView Select and get Item Focus automatically
I finally figure it out. Below is my current working code.
In the Model I have just changed the flag IsSelected to IsCurrent to avoid confusion with ListViewItem built-in property but it might just be an implementation detail.
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
The BindingList in ViewModel is mostly the same as in OP:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
NB : BindingList seems to reduce OnNotifyPropertyChange need but other Type of List should work with a tiny bit of extra code. I also noticed BindingList might not be suited for huge list scenario.
The View is then using the above ViewModel as DataContext and therefore Binding ItemSource to the BindingList. The ListViewItem Style Setter is then using the IsCurrent Property from the Model.
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
And finally this piece of View Code behind below is mainly to simulate the focus as per user input, otherwise the elemant get selected but not focused and might be outside the visible item scope :
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
WPF - Set Keyboard Focus to ListView Item like Tab does
You could try to focus the first item in the ListView
:
ListView.SelectedIndex = 0;
ListViewItem lvi = ListView.ItemContainerGenerator.ContainerFromIndex(0) as ListViewItem;
if (lvi != null)
lvi.Focus();
How to set focus on the first item of a ListView?
The problem is solved by calling UpdateLayout()
method before trying to get a ListViewItem
.
WPF: ListView Cannot Get Focus when Click in Blank Area
An easy workaround is to handle clicks:
<ListView PreviewMouseLeftButtonDown="ListView_PreviewMouseLeftButtonDown" ... />
to set focus
void ListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) =>
(sender as ListBox)?.Focus();
Note: this will cause GotFocus
to be called twice when clicking on normal items.
how to focus on the keyboard on a textbox in WPF listview after item.refresh
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();
}
}
}
WPF - Set Focus after click on ListView
You can do this directly in xaml without behaviors. In the Style
that you defined under ListView.ItemContainerStyle
, add a Trigger
. When the IsSelected
property of the ListViewItem
is true, apply focus to your txtPassword
TextBox element. Like this:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=txtPassword}"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
Set initial keyboard focus in Controls.ListView
The trick seems to be to explicitly set the focus to the item container of the first item in the ListView. I found a good explanation here.
Short summary of that blog post:
Because the items aren't available directly after creating the ListView focusing has to happen after all items are generated in the background. So, attach to the StatusChanged
event of the ItemContainerGenerator
:
Persons.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
And in the event handler, set the focus after everything has finished generating:
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (Persons.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
int index = Persons.SelectedIndex;
if (index >= 0)
((ListViewItem)Persons.ItemContainerGenerator.ContainerFromIndex(index)).Focus();
}
}
This solution isn't as simple as I hoped it would be but it worked for me.
WPF Listbox focus from viewmodel
I ended up with the following code in control's codebehind:
public void FixListboxFocus()
{
if (lbFiles.SelectedItem != null)
{
lbFiles.ScrollIntoView(lbFiles.SelectedItem);
lbFiles.UpdateLayout();
var item = lbFiles.ItemContainerGenerator.ContainerFromItem(viewModel.SelectedFile);
if (item != null && item is ListBoxItem listBoxItem && !listBoxItem.IsFocused)
listBoxItem.Focus();
}
}
This method is available for calling from within viewModel, which calls it every time it sets the selection:
var file = files.FirstOrDefault(f => f.Path.Equals(subfolderName, StringComparison.OrdinalIgnoreCase));
if (file != null)
SelectedFile = file;
else
SelectedFile = files.FirstOrDefault();
access.FixListboxFocus();
The access
is view passed to ViewModel via interface (to keep separation between presentation and logic). The relevant XAML part looks like following:
<ListBox x:Name="lbFiles" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" />
Related Topics
Using Extension Methods in .Net 2.0
Opening Process and Changing Window Position
Combine Two Linq Lambda Expressions
Show Authentication Dialog in C# for Windows Vista/7
How to Implement Inotifypropertychanged in C# 6.0
Get Powershell Command's Output When Invoked Through Code
No Itemchecked Event in a Checkedlistbox
How to Print a Text File on Thermal Printer Using Printdocument
How to Format 07/03/2012 to March 7Th,2012 in C#
Why Is Double.Nan Not Equal to Itself
Outofmemoryexception on Declaration of Large Array
Topmost Form, Clicking "Through" Possible
How to Make a Console Application Run Using Only a Single File in .Net Core
Invalidprogramexception/Common Language Runtime Detected an Invalid Program