WPF Focus,无法兼顾逻辑和键盘

时间:2016-10-09 18:02:20

标签: wpf mvvm focus

我已经浏览了几篇关于WPF和焦点的帖子,我能想到的最好的东西是我在代码中遗漏的东西。我正在使用严格的MVVM处理应用程序,因此我试图避免视图文件中的任何代码隐藏(在需要代码隐藏时使用附加行为),但此时甚至放置焦点代码在视图的代码隐藏中,它无法正常工作。

我有一个带有主窗口的应用程序,我试图在热键上弹出一个搜索窗口。我想只要用户点击热键,键盘焦点就会出现在搜索文本上,所以它只是热键,然后输入你的搜索词。除了逻辑焦点之外的所有事情都在这一点上起作用,即使键盘声称关注元素。

我似乎无法从代码中同时获取键盘和逻辑焦点。但是,如果我在搜索框出现后立即点击Tab,我就会直接进入文本框。

主窗口代码:

<ribbon:RibbonWindow x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    xmlns:attached="clr-namespace:UserInterface.Attached"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF45"
    xmlns:viewModels="clr-namespace:UserInterface.ViewModels"
    xmlns:views="clr-namespace:UserInterface.Views"
    xmlns:layout="clr-namespace:UserInterface.ViewModels.Layout"
    xmlns:layout1="clr-namespace:UserInterface.Views.Layout"
    MinHeight="560"
    MinWidth="950"
    WindowStartupLocation="CenterScreen" 
    Icon="{Binding Layout.IconPath}"
    DataContext="{Binding Main, Source={StaticResource Locator}}"
    FocusManager.FocusedElement="{Binding ElementName=LayoutControl}"
    Title="{Binding Layout.Title}">

<!-- Ribbon menu shortcuts  -->
<Window.InputBindings>
    <KeyBinding Modifiers="Control" Key="T" Command="{Binding Layout.Commands[GlobalObjectSearch]}" />
</Window.InputBindings>

<Grid>
    <ContentPresenter Content="{Binding Layout}" x:Name="LayoutControl">
        <ContentPresenter.Resources>
            <DataTemplate DataType="{x:Type layout:MainViewModel}">
                <layout1:MainView/>
            </DataTemplate>
        </ContentPresenter.Resources>
    </ContentPresenter>
</Grid>
</ribbon:RibbonWindow>

显示搜索窗口的代码:

    public SelfClosingDialogView ShowSelfClosingDialog(IWindowDialogViewModel dataContext)
    {
        dataContext.CheckWhetherArgumentIsNull(nameof(dataContext));

        var view = new SelfClosingDialogView
        {
            DataContext = dataContext,
            Owner = Application.Current?.MainWindow
        };

        view.Show();

        return view;
    }

搜索窗口代码(重用,如此通用):

<Window x:Class="UserInterface.Views.DialogViews.SelfClosingDialogView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:attached="clr-namespace:UserInterface.Attached"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    mc:Ignorable="d"
    SizeToContent="WidthAndHeight"
    WindowStyle="None"
    WindowStartupLocation="CenterOwner">

<!-- Allow view models to cause the window to close -->
<Window.Style>
    <Style TargetType="{x:Type Window}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsClosed}" Value="true">
                <!-- Executes close -->
                <Setter Property="attached:WindowCloseBehavior.Close" Value="true" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Style>

<!-- Displays the passed-in view model -->
<Grid>
    <ContentPresenter x:Name="DialogPresenter" Content="{Binding}" Margin="0" />
</Grid>
</Window>

我的搜索视图代码:

<UserControl x:Class="UserInterface.Views.DialogViews.ObjectSearchView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:dialogViewModels="clr-namespace:UserInterface.ViewModels.DialogViewModels"
         xmlns:utils="clr-namespace:WPF.Utils"
         xmlns:attached="clr-namespace:UserInterface.Attached"
         mc:Ignorable="d"
         d:DataContext="{d:DesignInstance dialogViewModels:ObjectSearchViewModel}"
         MinWidth="250"
         Focusable="True"
         FocusManager.IsFocusScope="True">
<UserControl.InputBindings>
    <KeyBinding Key="Enter" Command="{Binding BrowseToObjectCommand}" />
    <KeyBinding Key="Escape" Command="{Binding CloseWindowCommand}" />
</UserControl.InputBindings>
<UserControl.Resources>
    <Style BasedOn="{StaticResource FormTextBlockStyle}" TargetType="TextBlock" />
</UserControl.Resources>

<StackPanel>
    <TextBox Name="SearchText" 
             Focusable="True"
             Text="{utils:ValidatingLiveBinding SearchText}" 
             attached:NavigatingListBoxBehavior.LinkedListBox="{Binding ElementName=SearchResults}">
    </TextBox>
    <ScrollViewer HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Auto"
                  MaxHeight="400">
        <ListBox Name="SearchResults"
                 ItemsSource="{Binding SearchResults}" 
                 SelectedItem="{Binding SelectedSearchItem}" 
                 Visibility="{Binding HasSearchResults, Converter={StaticResource BooleanToVisibilityConverter}}"
                 attached:ItemsControlProperties.DoubleClickCommand="{Binding BrowseToObjectCommand}"
                 KeyboardNavigation.IsTabStop="False"
                 IsSynchronizedWithCurrentItem="True" />
    </ScrollViewer>
</StackPanel>
</UserControl>

最后,代码隐藏的黑客我试图获得焦点(加上调试代码,以便我不会失去焦点切换到Visual Studio):

public partial class ObjectSearchView : UserControl
{
    public ObjectSearchView()
    {
        InitializeComponent();
        this.Loaded += this.OnLoad;
    }

    private void OnLoad(object sender, RoutedEventArgs e)
    {
        this.PrintFocusInfo();
        FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), this.SearchText);
        this.PrintFocusInfo();
        this.SearchText.Focus();
        this.PrintFocusInfo();
        Keyboard.Focus(this.SearchText);
        this.PrintFocusInfo();
    }

    [Conditional("DEBUG")]
    private void PrintFocusInfo()
    {
        var logicalElement = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this.SearchText));
        Debug.WriteLine("Current logical focus is on '{0}', of type '{1}' ({2})".FormatInvariantCulture((logicalElement as FrameworkElement)?.Name, logicalElement?.GetType().Name, logicalElement));
        var focusedElement = Keyboard.FocusedElement;
        Debug.WriteLine(
            "Current Keyboard Focus is on '{0}', of type '{1}' ({2})".FormatInvariantCulture(
                (focusedElement as FrameworkElement)?.Name,
                focusedElement.GetType().Name,
                focusedElement));
    }
}

输出窗口内容:

Current logical focus is on '', of type '' ()
Current Keyboard Focus is on '', of type 'MainWindow' (UserInterface.Views.MainWindow)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on '', of type 'MainWindow' (UserInterface.Views.MainWindow)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on 'SearchText', of type 'TextBox' (System.Windows.Controls.TextBox)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on 'SearchText', of type 'TextBox' (System.Windows.Controls.TextBox)

我试图包含我能想到的所有内容,但除了null之外,我无法合理地展示任何内容。

1 个答案:

答案 0 :(得分:1)

这是我最终创建的行为,为我解决了这个问题。还有很多我不知道为什么这个有用......但是如果你在使Focus聚焦合作时遇到问题,那么当IsVisible设置为true并让Dispatcher设置焦点时,关键就是捕获它为了你。我将此事件链接到文本框上的IsVisibleChanged元素(通过附加行为)。

    private void SetFocusOnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() => this.AssociatedObject.Focus()));
        }
    }