WPF Listbox焦点从ViewModel

时间:2019-07-15 06:54:03

标签: c# wpf listbox focus

我偶然发现了Listbox和focus的一个众所周知的问题。我正在从视图模型中设置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和StackOverflow上阅读了很多东西,但是我发现的所有答案都涉及手动填充列表框,而不是通过viewmodel的绑定。所以问题是:如何在展示的场景中适当地集中新选择的物品?


要添加一些上下文,我正在实现侧边栏文件浏览器:

Explorer

我需要在树状视图下方的列表框中导航键盘。

2 个答案:

答案 0 :(得分:0)

以下是可能适合您的解决方案:

控件:

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
}

ViewModel:

 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");

答案 1 :(得分:0)

我最终在控件的代码后面添加了以下代码:

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}" />