来自视图模型的 WPF 列表框焦点
WPF Listbox focus from viewmodel
我偶然发现了列表框和焦点的众所周知的问题。我正在从视图模型中设置 ItemsSource
,在某些时候我需要重新加载它们并将选择和焦点设置到特定项目,比如:
private readonly ObservableCollection<ItemViewModel> items;
private ItemViewModel selectedItem;
private void Process()
{
items.Clear();
for (int i = 0; i < 100; i++)
{
items.Add(new ItemViewModel(i));
}
var item = items.FirstOrDefault(i => i.Value == 25);
SelectedItem = item;
}
public ObservableCollection<ItemViewModel> Items { /* usual stuff */ }
public ItemViewModel SelectedItem { /* usual stuff */ }
绑定可能如下所示:
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
调用方法后项目被选中,但没有获得焦点。
我在 Internet 和 Whosebug 上阅读了很多内容,但我找到的所有答案都涉及手动填充列表框,而不是通过视图模型绑定。所以问题是:如何在呈现的场景中正确地关注新选择的项目?
为了添加一些上下文,我正在实现一个边栏文件浏览器:
我需要在树视图下方的列表框中进行键盘导航。
这里有一个可能适合您的解决方案:
控件:
class FocusableListBox : ListBox
{
#region Dependency Proeprty
public static readonly DependencyProperty IsFocusedControlProperty = DependencyProperty.Register("IsFocusedControl", typeof(Boolean), typeof(FocusableListBox), new UIPropertyMetadata(false, OnIsFocusedChanged));
public Boolean IsFocusedControl
{
get { return (Boolean)GetValue(IsFocusedControlProperty); }
set { SetValue(IsFocusedControlProperty, value); }
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ListBox listBox = dependencyObject as ListBox;
listBox.Focus();
}
#endregion Dependency Proeprty
}
视图模型:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Boolean _IsFocused;
private String selectedItem;
public ObservableCollection<String> Items { get; private set; }
public String SelectedItem
{
get
{
return selectedItem;
}
set
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public Boolean IsFocused
{
get { return _IsFocused; }
set
{
_IsFocused = value;
RaisePropertyChanged("IsFocused");
}
}
public ViewModel()
{
Items = new ObservableCollection<string>();
Process();
}
private void Process()
{
Items.Clear();
for (int i = 0; i < 100; i++)
{
Items.Add(i.ToString());
}
ChangeFocusedElement("2");
}
public void ChangeFocusedElement(string newElement)
{
var item = Items.FirstOrDefault(i => i == newElement);
IsFocused = false;
SelectedItem = item;
IsFocused = true;
}
private void RaisePropertyChanged(String propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
XAML:
<local:FocusableListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Width="200"
IsFocusedControl="{Binding IsFocused, Mode=TwoWay}"/>
更新调用:
_viewModel.ChangeFocusedElement("10");
我在控件的代码隐藏中得到了以下代码:
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();
}
}
此方法可从 viewModel 中调用,每次设置选择时都会调用它:
var file = files.FirstOrDefault(f => f.Path.Equals(subfolderName, StringComparison.OrdinalIgnoreCase));
if (file != null)
SelectedFile = file;
else
SelectedFile = files.FirstOrDefault();
access.FixListboxFocus();
access
是通过接口传递给 ViewModel 的视图(以保持表示和逻辑之间的分离)。相关的 XAML 部分如下所示:
<ListBox x:Name="lbFiles" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" />
我偶然发现了列表框和焦点的众所周知的问题。我正在从视图模型中设置 ItemsSource
,在某些时候我需要重新加载它们并将选择和焦点设置到特定项目,比如:
private readonly ObservableCollection<ItemViewModel> items;
private ItemViewModel selectedItem;
private void Process()
{
items.Clear();
for (int i = 0; i < 100; i++)
{
items.Add(new ItemViewModel(i));
}
var item = items.FirstOrDefault(i => i.Value == 25);
SelectedItem = item;
}
public ObservableCollection<ItemViewModel> Items { /* usual stuff */ }
public ItemViewModel SelectedItem { /* usual stuff */ }
绑定可能如下所示:
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
调用方法后项目被选中,但没有获得焦点。
我在 Internet 和 Whosebug 上阅读了很多内容,但我找到的所有答案都涉及手动填充列表框,而不是通过视图模型绑定。所以问题是:如何在呈现的场景中正确地关注新选择的项目?
为了添加一些上下文,我正在实现一个边栏文件浏览器:
我需要在树视图下方的列表框中进行键盘导航。
这里有一个可能适合您的解决方案:
控件:
class FocusableListBox : ListBox
{
#region Dependency Proeprty
public static readonly DependencyProperty IsFocusedControlProperty = DependencyProperty.Register("IsFocusedControl", typeof(Boolean), typeof(FocusableListBox), new UIPropertyMetadata(false, OnIsFocusedChanged));
public Boolean IsFocusedControl
{
get { return (Boolean)GetValue(IsFocusedControlProperty); }
set { SetValue(IsFocusedControlProperty, value); }
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ListBox listBox = dependencyObject as ListBox;
listBox.Focus();
}
#endregion Dependency Proeprty
}
视图模型:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Boolean _IsFocused;
private String selectedItem;
public ObservableCollection<String> Items { get; private set; }
public String SelectedItem
{
get
{
return selectedItem;
}
set
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public Boolean IsFocused
{
get { return _IsFocused; }
set
{
_IsFocused = value;
RaisePropertyChanged("IsFocused");
}
}
public ViewModel()
{
Items = new ObservableCollection<string>();
Process();
}
private void Process()
{
Items.Clear();
for (int i = 0; i < 100; i++)
{
Items.Add(i.ToString());
}
ChangeFocusedElement("2");
}
public void ChangeFocusedElement(string newElement)
{
var item = Items.FirstOrDefault(i => i == newElement);
IsFocused = false;
SelectedItem = item;
IsFocused = true;
}
private void RaisePropertyChanged(String propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
XAML:
<local:FocusableListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Width="200"
IsFocusedControl="{Binding IsFocused, Mode=TwoWay}"/>
更新调用:
_viewModel.ChangeFocusedElement("10");
我在控件的代码隐藏中得到了以下代码:
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();
}
}
此方法可从 viewModel 中调用,每次设置选择时都会调用它:
var file = files.FirstOrDefault(f => f.Path.Equals(subfolderName, StringComparison.OrdinalIgnoreCase));
if (file != null)
SelectedFile = file;
else
SelectedFile = files.FirstOrDefault();
access.FixListboxFocus();
access
是通过接口传递给 ViewModel 的视图(以保持表示和逻辑之间的分离)。相关的 XAML 部分如下所示:
<ListBox x:Name="lbFiles" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" />