在 Popup 控件中的任意位置点击时文本框不会失去焦点

Textbox not losing focus when tapping anywhere inside Popup control

我有一个带有文本框和按钮控件的弹出窗口。 当我 click/tap 在 Popup 中的任何地方时,Textbox 不会失去焦点。 使文本框失去焦点的唯一方法是将焦点设置在按钮上。 我希望能够在不关闭弹出窗口或单击按钮的情况下从文本框中移除焦点。 此问题仅发生在 Popup 中。 我曾经将控件放在 Flyout 中,当用户在文本框外和 Flyout 内的任何地方单击时,文本框就会失去焦点。此外,当 Flyout 打开时,Textbox 会自动将焦点放在 Flyout 打开上。

我测试了文本框如何在新的空白 UWP 项目中失去焦点的不同行为,结果是一样的。还直接在根网格中测试了文本框的行为。这是 MainPage 中的 XAML:

<Page x:Class="App1.MainPage"
  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:local="using:App1"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <StackPanel Margin="0,40"
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Orientation="Horizontal">
        <Button Margin="20,0"
                Content="Popup button"
                Tapped="Button_Tapped" />
        <Button Margin="20,0" Content="Flyout button">
            <Button.Flyout>
                <Flyout>
                    <Grid Width="200">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <TextBox VerticalAlignment="Center" />
                        <Button Grid.Column="1" Content="Test" />
                    </Grid>
                </Flyout>
            </Button.Flyout>
        </Button>
    </StackPanel>
    <Popup x:Name="MyPopup"
           Width="320"
           Height="60"
           IsLightDismissEnabled="True">
        <Grid Width="320"
              Height="60"
              Background="WhiteSmoke"
              Padding="12,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <TextBox VerticalAlignment="Center" />
            <Button Grid.Column="1" Content="Test" />
        </Grid>
    </Popup>
    <TextBox Grid.Row="1"
             MinWidth="64"
             MaxWidth="300"
             Margin="0,40"
             VerticalAlignment="Center" />
</Grid>
</Page>

以及打开弹出窗口的事件:

private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
    MyPopup.IsOpen = true;
}

问题是 Grid 不是 "focusable",因为它不是从 Control 派生的。一种方法是将 Grid 放在 ContentControlContentPresenter.

另一种方法有点麻烦,但您可以在点击 Popup 内的 Grid 时以编程方式将焦点设置在按钮上:

<Popup
    x:Name="MyPopup"
    Width="320"
    Height="60"
    IsLightDismissEnabled="True">
    <Grid
        x:Name="Grid"
        Width="320"
        Height="60"
        Background="WhiteSmoke"
        Tapped="Grid_OnTapped"
        Padding="12,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <TextBox VerticalAlignment="Center" />
        <Button
            Grid.Column="1"
            Content="Test" />
    </Grid>
</Popup>

还有 Tapped 处理程序,您通常在其中搜索任何不是 TextBox 的可聚焦控件。如果你没有,你可以放置一个不可见的 ContentControlOpacity=0.01 例如,这将成为焦点元素:

    private void Grid_OnTapped(object sender, TappedRoutedEventArgs e)
    {
        var grid = sender as Grid;
        if(grid == null) return;
        var controlToFocus = FindChild<Button>(grid);
        controlToFocus.Focus(FocusState.Programmatic);
    }

    private static T FindChild<T>(DependencyObject parent) where T : DependencyObject
    {
        var childCount = VisualTreeHelper.GetChildrenCount(parent);
        for (var i = 0; i < childCount; i++)
        {
            var elt = VisualTreeHelper.GetChild(parent, i);
            if (elt is T) return (T)elt;
            var result = FindChild<T>(elt);
            if (result != null) return result;
        }

        return null;
    }