みかづきメモ

学習したことのメモとか、日記とか、備忘録。

UWP で タッチキーボードの上に要素を表示する

新しいノートパソコン超快適で幸せです。

UWP の入力フォームなどで、タッチキーボード*1を表示すると、隠れてしまう要素があります。
しかしながら、ガイドラインにもあるように、常に表示しておくのが良い要素もあります。

私が探した限りでは、標準で楽に実装できるものがなさそうだったので、
やり方をメモしておきます。


キーボードに隠れると困る要素というのは、例えば、送信フォームの「送信」ボタンだとか。
Twitter で言えば、画像添付だとか、そういうのは隠れると、ちょっと不便になってしまいます。

ちょうど、 Twitter 公式アプリも、そういった要素はキーボードで隠れないようにされています。
ということで、早速実装。

<!-- @ UniversalApp10/Views/Pages/TweetPage.xaml -->
<Page x:Class="UniversalApp10.Views.Pages.TweetPage"
      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:mvvm="using:Prism.Windows.Mvvm"
      xmlns:vm="using:UniversalApp10.ViewModels.Pages"
      d:DataContext="{d:DesignInstance vm:TweetPageViewModel}"
      d:DesignHeight="300"
      d:DesignWidth="400"
      mvvm:ViewModelLocator.AutoWireViewModel="True"
      mc:Ignorable="d">

    <RelativePanel x:Name="Root" VerticalAlignment="Stretch">
        <RelativePanel x:Name="Header"
                       RelativePanel.AlignLeftWithPanel="True"
                       RelativePanel.AlignRightWithPanel="True"
                       RelativePanel.AlignTopWithPanel="True">
            <Grid x:Name="TweetCounter"
                  Height="64"
                  RelativePanel.AlignRightWithPanel="True">
                <TextBlock Margin="0,0,10,0"
                           VerticalAlignment="Center"
                           FontSize="15"
                           Text="{x:Bind ViewModel.WhisperCount, Mode=TwoWay}" />
            </Grid>
            <StackPanel Height="64"
                        RelativePanel.AlignLeftWithPanel="True"
                        RelativePanel.LeftOf="TweetCounter"
                        Orientation="Horizontal">
                <Image Width="48"
                       Height="48"
                       Source="{x:Bind ViewModel.User.Icon}" />
                <StackPanel Margin="10,0">
                    <TextBlock FontSize="16" Text="{x:Bind ViewModel.User.Name}" />
                    <TextBlock FontSize="14" Foreground="Silver" Text="{x:Bind ViewModel.User.ScreenName}" />
                </StackPanel>
            </StackPanel>
        </RelativePanel>
        <Grid x:Name="Footer"
              Height="32"
              RelativePanel.AlignBottomWithPanel="True"
              RelativePanel.AlignLeftWithPanel="True"
              RelativePanel.AlignRightWithPanel="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="1*" />
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0"
                    HorizontalAlignment="Stretch"
                    Content="@" />
            <Button Grid.Column="1"
                    HorizontalAlignment="Stretch"
                    Content="#" />
            <Button Grid.Column="2" HorizontalAlignment="Stretch">
                <Button.Content>
                    <FontIcon Glyph="&#xE722;" />
                </Button.Content>
            </Button>
            <Button Grid.Column="3"
                    HorizontalAlignment="Stretch"
                    Background="{StaticResource SystemControlHighlightAccentBrush}"
                    Content="Tweet" />
        </Grid>
        <TextBox HorizontalAlignment="Stretch"
                 VerticalAlignment="Stretch"
                 AcceptsReturn="True"
                 RelativePanel.Above="Footer"
                 RelativePanel.AlignLeftWithPanel="True"
                 RelativePanel.AlignRightWithPanel="True"
                 RelativePanel.Below="Header"
                 TextWrapping="Wrap" />
    </RelativePanel>
</Page>

長いですが、なんとなく公式 Twitter アプリのツイート画面っぽいレイアウトになっています。
次は、コードビハインド側です。

// @ UniversalApp10/Views/TweetPage.xaml.cs

using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

using UniversalApp10.ViewModels.Pages;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace UniversalApp10.Views.Pages
{
    public sealed partial class TweetPage : Page
    {
        private readonly InputPane _inputPane;
        private double? _rootHeight;

        public TweetPage()
        {
            InitializeComponent();
            _inputPane = InputPane.GetForCurrentView();
            _rootHeight = null;
        }

        public TweetPageViewModel ViewModel => DataContext as TweetPageViewModel;

        private void OnKeyboardShowing(InputPane sender, InputPaneVisibilityEventArgs args)
        {
            _rootHeight = !_rootHeight.HasValue ? Root.ActualHeight : _rootHeight;
            Root.Height = _rootHeight.Value- sender.OccludedRect.Height; // *
            Root.VerticalAlignment = VerticalAlignment.Top;
        }

        private void OnKeyboardHiding(InputPane sender, InputPaneVisibilityEventArgs args)
        {
            if (!_rootHeight.HasValue)
                return;
            Root.Height = _rootHeight.Value;
            Root.VerticalAlignment = VerticalAlignment.Stretch;
        }

        #region Overrides of Page

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            _inputPane.Hiding += OnKeyboardHiding;
            _inputPane.Showing += OnKeyboardShowing;
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            _inputPane.Hiding -= OnKeyboardHiding;
            _inputPane.Showing -= OnKeyboardShowing;
        }

        #endregion Overrides of Page
    }
}

ガイドラインにあるとおり、 InputPane のイベントで処理を行います。

OnKeyboardShowing では、元の高さの保存と、キーボード分の高さの調節を行っています。
また、なぜか、 OnKeyboardHiding が呼ばれずに、再度 OnKeyboardShowing
呼ばれることがあったので、一応元の高さを使って計算しています。

OnKeyboardHiding では、キーボードがない状態へと、要素の高さを復元しています。
上の通り、たまに呼ばれません。イミフです。

これで、タッチキーボードの上に、何らかの要素(今回の場合は、 Footer ) が表示されます。

なお、 CommandBar を一番下に表示しているなどで、
Root.Height != Window.Current.Bounds.Height な場合は、
* が付いてるところで調節を行えば、うまい具合に表示することが可能です。

ということで、ではでは~。(他に実装方法があったら教えて下さい)

*1:ソフトウェアキーボードやスクリーンキーボードともいう