文字列のコピーをサポートするには?[Win 8]WinRT/Metro TIPS

Windowsストア・アプリに表示しているテキスト・データをユーザーにコピーさせたい場合、クリップボードにコピーすればよい。その実装方法を説明する。

» 2012年12月06日 14時13分 公開
[山本康彦BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次はこちら

 Windowsストア・アプリに表示しているデータをユーザーにコピーさせたいことがある。テキストボックスであれば、クリップボードへのコピー操作がサポートされている。マウス操作では、文字列を選択して[Ctrl]+[C]キーか、あるいは右クリックで出てくるコンテキスト・メニューの[コピー]を使えばよい。タッチ操作でも、文字列を1回タップすればグリッパーが出てくるので、それを使って範囲を指定し、もう1度タップして出てきたコンテキスト・メニューからコピーを選べばよい(下に掲載した参考画像を参照)。

 それではテキストボックス以外のコントロールでは、どうしたらよいだろうか? 本稿では、Windowsストア・アプリで文字列のコピーを実装する方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #16」からダウンロードできる。

参考画像:テキストボックスの文字列をタッチ操作でコピーする様子
(上)タッチ操作のとき、テキストボックスの文字列にタップするとグリッパー(丸いハンドル)が出る。グリッパーをドラッグすることで、文字列の選択範囲を変更できる。
(下)文字列が選択されている部分かグリッパーをタップすると、コンテキスト・メニューが出る。[コピー]を選べば、選択した文字列がクリップボードにコピーされる。

事前準備

 Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。

 なお、今回のソース・コードはVS 2012の「グリッド アプリケーション (XAML)」テンプレートをベースにしている。本稿中に登場するソース・コードのファイル名などは、そのテンプレートで自動生成されたものである。

TextBlockなどをコピー可能にするには?

 TextBlockコントロールやRichTextBlockコントロールなどの文字列を表示するためのコントロールは、クリップボードへのコピー操作をサポートしている。IsTextSelectionEnabledプロパティをTrueに設定するだけで、コピーが可能になるのだ。

 例として、ItemDetailPage.xamlファイル中のRichTextBlockコントロールで試してみよう。

<common:RichTextColumns x:Name="richTextColumns" Margin="117,0,117,47">
  <RichTextBlock x:Name="richTextBlock" Width="560"
      Style="{StaticResource ItemRichTextStyle}"
      IsTextSelectionEnabled="True"><!-- False から True に変更 -->
    <Paragraph>
      <Run FontSize="26.667" ……以下略……


ItemDetailPage.xamlファイル中のRichTextBlockコントロールをコピー可能にする(70行目付近)


 実行してみると、テキストボックスと同じようにコピー操作ができるようになっている(次の画像)。なお、入力が不可能なコントロールなので、タッチ・キーボードは表示されないし、コンテキスト・メニューにも[貼り付け]などはない。

IsTextSelectionEnabledプロパティをTrueにすると、コピー操作が可能になった

GridViewでも同じようにすると……?

 では、GridViewコントロールやListViewコントロールなどでも同様にすればよいだろうか? そのItemTemplateの中にあるTextBlockコントロールなどのIsTextSelectionEnabledプロパティをTrueにすれば、コピー可能になるはずだ。

 確かにそれでコピーできるようになる。例として、筆者がストアに公開しているアプリを挙げておこう(次の画像)。

GridViewコントロールの中にあるTextBlockコントロールでも、IsTextSelectionEnabledプロパティをTrueにすれば、TextBoxコントロールと同様なコピー操作が可能になる(筆者のアプリの一部分)

 しかしこの方法は、重大な問題を引き起こすことがある。GridViewコントロールのアイテムをタップ/クリックしてほかの画面へ遷移するアプリでは、文字列部分をタップ/クリックするとコピー操作になってしまって遷移できなくなるのだ。上のアプリのように画面遷移がないのであれば構わないが、そうでないならばこの方法は使えない。

 MSDNのガイドラインでは、タップの長押しやマウスの右クリックでコンテキスト・メニューを出すのがよいとされている。以下、その方法を順番に説明していく。

コードから文字列をクリップボードにコピーするには?

 Windows.ApplicationModel.DataTransfer名前空間のDataPackageクラスとClipboardクラスを使って、次のようなコードを記述すればよい。

// 文字列をクリップボードにコピーする
private void CopyToClipBoard(string content)
{
  var package = new Windows.ApplicationModel.DataTransfer.DataPackage();
  package.SetText(content);
  Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(package);
}


' 文字列をクリップボードにコピーする
Private Sub CopyToClipBoard(content As String)
  Dim package = New Windows.ApplicationModel.DataTransfer.DataPackage()
  package.SetText(content)
  Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(package)
End Sub


文字列をクリップボードにコピーするコード(上:C#、下:VB)


コードからコンテキスト・メニューを出すには?

 Windows.UI.Popups名前空間のPopupMenuクラスを使う。個々のメニューには、UICommandクラス(同じくWindows.UI.Popups名前空間)を使う。UICommandには、メニューとして表示する文字列と、選択されたときに実行するコードを一緒にセットする。なお、PopupMenuクラスのShowForSelectionAsyncメソッドを使ってコンテキスト・メニューを出す際には、対象とするコントロールの領域を引数として与えねばならない。

// コンテキスト・メニューを出し、クリックされたらコピーを実行する
private async void ShowCopyPopup(string content, FrameworkElement target)
{
  // コンテキスト・メニューを作成する
  var menu = new Windows.UI.Popups.PopupMenu();
  menu.Commands.Add(
    new Windows.UI.Popups.UICommand(
                           "コピー",                           (cmd) => {
                             // [コピー]が選択されたときに実行されるコード
                             CopyToClipBoard(content);
                           })
  );

  // コンテキスト・メニューを表示する
  await menu.ShowForSelectionAsync(GetElementRect(target));
}

// UIコントロールの領域を算出する
private static Rect GetElementRect(FrameworkElement element)
{
  GeneralTransform buttonTransform = element.TransformToVisual(null);
  Point point = buttonTransform.TransformPoint(new Point());
  return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
}


Private Async Sub ShowCopyPopup(content As String, target As FrameworkElement)
  ' コンテキスト・メニューを作成する
  Dim menu = New Windows.UI.Popups.PopupMenu()
  menu.Commands.Add(
      New Windows.UI.Popups.UICommand(
                              "コピー",                              Sub(cmd)
                                ' [コピー]が選択されたときに実行されるコード
                                CopyToClipBoard(content)
                              End Sub
    )
  )

  ' コンテキスト・メニューを表示する
  Await menu.ShowForSelectionAsync(GetElementRect(target))
End Sub

' UIコントロールの領域を算出する
Private Shared Function GetElementRect(element As FrameworkElement) As Rect
  Dim buttonTransform As GeneralTransform = element.TransformToVisual(Nothing)
  Dim point As Point = buttonTransform.TransformPoint(New Point())
  Return New Rect(Point, New Size(element.ActualWidth, element.ActualHeight))
End Function


コンテキスト・メニューを出し、クリックされたらコピーを実行するコード(上:C#、下:VB)


長押し/右クリックでコピーできるようにするには?

 例としてGroupDetailPage画面でやってみよう。まずXAMLコードでイベント・ハンドラを設定する。そのためには、StandardStyles.xamlファイル中の「Standard500x130ItemTemplate」という名称のDataTemplateを、GroupDetailPage.xamlファイルの<Page.Resources>要素内にコピーし、新しい名前を付ける(ここでは「MyItemTemplate」とした)。次に、GroupDetailPage.xamlファイル内で「Standard500x130ItemTemplate」を使っている箇所を探し、「MyItemTemplate」に変更する。

 そうしたら、次のコードのようにMyItemTemplateの中のGridコントロールにイベント・ハンドラを2つ設定する。Holdingイベント(=長押し)とRightTappedイベント(=右クリック)である。

<DataTemplate x:Key="MyItemTemplate">
  <Grid Height="110" Width="480" Margin="10"
      Holding="itemGridView_Holding" RightTapped="itemGridView_RightTapped">
    <Grid.ColumnDefinitions ……以下略……


ItemTemplateとして使うDataTemplateに含まれるGridコントロールに、イベント・ハンドラを2つ設定した


 このイベントはItemTemplate内のコントロールに付けたので、GridViewコントロールの全体ではなく、個々のアイテムごとに発生することに注意してほしい。また、このGridコントロールにバインドされるデータも、SampleDataSourceオブジェクトの全体ではなく、その中の個々のSampleDataItemオブジェクトである。

 イベント・ハンドラの実装は次のようになる。注意点が2つある。

 まず、Holdingイベントは、「開始」(=Started)と「終了」(=Completed)(または「キャンセル」(=Canceled))の2回発生することだ。今回は、開始時だけコンテキスト・メニューを出せばよい。もう1点は、Holdingイベントが終わった後に続いてRightTappedイベントも発生することだ。長押しすると、HoldingイベントもRightTappedイベントも発生するので、重複して処理を実行してしまわないような対策が必要だ。

// GridView のアイテムが長押しされたときのイベント・ハンドラ
private void itemGridView_Holding(object sender, HoldingRoutedEventArgs e)
{
  if (e.HoldingState != Windows.UI.Input.HoldingState.Started)
    return;  // Completed や Canceled のときは何もしない

  // コピーのコンテキスト・メニューを出す
  ShowCopyPopup(sender as Grid, e.OriginalSource as FrameworkElement);

  e.Handled = true;
}

// GridView のアイテムが右クリックされたときのイベント・ハンドラ
private void itemGridView_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
  // Holding の後にも RightTapped が発生するので、二重実行しない対策が必要
  if (e.PointerDeviceType != Windows.Devices.Input.PointerDeviceType.Mouse)
    return; // Touch のときは、itemGridView_Holding で実行済み

  // コピーのコンテキスト・メニューを出す
  ShowCopyPopup(sender as Grid, e.OriginalSource as FrameworkElement);

  e.Handled = true;
}


// コンテキスト・メニューを出し、クリックされたらコピーを実行する
private void ShowCopyPopup(Grid gridInItem,  FrameworkElement target)
{
  // ItemTemplate の Grid にバインドされている DataContext は SampleDataItem
  SampleDataItem selected = gridInItem.DataContext as SampleDataItem;

  // クリップボードへコピーする内容
  var content = string.Format(
@"{0}
{1}
{2}
{3}",selected.Group, selected.Title, selected.Subtitle, selected.Description);

  // コンテキスト・メニューを作成して表示する
  ShowCopyPopup(content, target);
}


' GridView のアイテムが長押しされたときのイベント・ハンドラ
Private Sub itemGridView_Holding(sender As Object, e As HoldingRoutedEventArgs)
  If (e.HoldingState <> Windows.UI.Input.HoldingState.Started) Then
    Return  ' Completed や Canceled のときは何もしない
  End If

  ' コピーのコンテキスト・メニューを出す
  ShowCopyPopup(DirectCast(sender, Grid), _
                DirectCast(e.OriginalSource, FrameworkElement))

  e.Handled = True
End Sub

' GridView のアイテムが右クリックされたときのイベント・ハンドラ
Private Sub itemGridView_RightTapped(sender As Object, e As RightTappedRoutedEventArgs)
  ' Holding の後にも RightTapped が発生するので、二重実行しない対策が必要
  If (e.PointerDeviceType <> Windows.Devices.Input.PointerDeviceType.Mouse) Then
    Return ' Touch のときは、itemGridView_Holding で実行済み
  End If

  ' コピーのコンテキスト・メニューを出す
  ShowCopyPopup(DirectCast(sender, Grid), DirectCast(e.OriginalSource, FrameworkElement))

  e.Handled = True
End Sub


' コンテキスト・メニューを出し、クリックされたらコピーを実行する
Private Sub ShowCopyPopup(gridInItem As Grid, target As FrameworkElement)
  ' ItemTemplate の Grid にバインドされている DataContext は SampleDataItem
  Dim selected As Data.SampleDataItem _
        = DirectCast(gridInItem.DataContext, Data.SampleDataItem)

  ' クリップボードへコピーする内容
  Dim content = selected.Group.ToString() + vbCrLf _
              + selected.Title + vbCrLf _
              + selected.Subtitle + vbCrLf _
              + selected.Description

  ' コンテキスト・メニューを作成して表示する
  ShowCopyPopup(Content, target)
End Sub


長押し/右クリックでコンテキスト・メニューを出し、クリックされたらコピーを実行するコード(上:C#、下:VB)


 これで完成である。

実行結果

 以上を実装して実行してみると、次の画像のようになる。

実行結果画面
右端2段目のアイテムを右クリックしたところ(実行画面)

 タップの長押し/マウスの右クリックで[コピー]というコンテキスト・メニューが現れ、それを選択するとアイテムのタイトルや説明がクリップボードにコピーされる。その内容は、テキスト・エディタなどに切り替えてペーストして確認できる(冒頭の参考画像に見えるテキストは、そのようにしてペーストしたものだ)。

まとめ

 クリップボードへの文字列コピーをサポートするには、TextBlockコントロールなど文字列を表示するだけのコントロールでは、IsTextSelectionEnabledプロパティをTrueに設定すればよい。GridViewコントロールなど、タッチ/クリックがコマンド・トリガーになっている場合は、長押し/右クリックでコピーできるように実装する。また、本稿では説明しなかったが、GridViewコントロールなどでアイテムが選択状態にあるときは、キーボード操作[Ctrl]+[C]にも対応させる。

 クリップボードへのコピーのガイドラインや、タップ操作/マウス操作については、次のドキュメントを参考にしてほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。