SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

UWPアプリ開発の最前線

クリップボードビューアを作ろう!
~Windows 10 1809の新機能「クリップボード履歴」にアクセス

UWPアプリ開発の最前線 第10回

クリップボード履歴を取得する

 それでは実際にAPIを使ってみましょう。まずは、履歴を取得するところから。サンプルアプリでいうと、次の画像の赤枠内に表示しているデータを取ってくる部分です。

サンプルアプリ
サンプルアプリ(赤枠内が履歴を表示している部分)

履歴リストを取得する基本

 クリップボードの履歴リストは、GetHistoryItemsAsyncメソッドで取得できます。それぞれの履歴アイテムはClipboardHistoryItemクラス(Windows.ApplicationModel.DataTransfer名前空間)になっていて、その中にIdプロパティ(文字列型)とTimestampプロパティ(DateTimeOffset型)、そしてContentプロパティ(Windows.ApplicationModel.DataTransfer名前空間のDataPackageViewクラス)が入っています。

履歴リストと履歴アイテム
クリップボードの履歴リストと履歴アイテム

 DataPackageViewクラスはクリップボードそのものの内容を表す型でもあります。ただし、DataPackageViewオブジェクトに格納されているデータは、クリップボードと履歴リストに入っているものとで違いがあります。クリップボードの内容の全てが履歴に保存されるわけではありません。

 履歴を取得する処理の基本的な流れは次のコードのようになります。

履歴を取得する処理の基本的な流れを示すメソッド
public static async Task GetClipboardHistoryAsync()
{
  if (!Clipboard.IsHistoryEnabled())
    return; // 設定で履歴が無効にされている

  // 履歴リストを取得する
  var result = await Clipboard.GetHistoryItemsAsync();
  // 履歴の取得に失敗したときは、result.Statusに
  // Success以外(AccessDeniedまたはClipboardHistoryDisabled)が返される
  if (result.Status != ClipboardHistoryItemsResultStatus.Success)
    return;

  // 履歴のリストを取り出す
  IReadOnlyList<ClipboardHistoryItem> historyList = result.Items;

  // それぞれの履歴アイテムを処理する
  foreach (ClipboardHistoryItem item in historyList)
  {
    // 履歴アイテムのIDとタイムスタンプ
    string id = item.Id;
    DateTimeOffset timestamp = item.Timestamp;

    // データのパッケージを取り出す(クリップボードのデータと同じ型)
    DataPackageView content = item.Content;

    // 以降は、履歴アイテムでもクリップボードそのもののデータでも同じ流れ

    // テキストデータを取り出す例
    if (content.Contains(StandardDataFormats.Text))
    {
      string textData = await content.GetTextAsync();
      // DataPackageViewに入っているデータは、
      // このようにして非同期メソッドを使って取得する
    }

    // データとして入っているフォーマットの一覧
    List<string> formats = content.AvailableFormats.ToList();
    // content.AvailableFormatsはSystem.__ComObjectのリストなので、
    // ToList拡張メソッドを使って「こっちの世界に固定する」

    // 全てのデータを取りだす例
    foreach (string format in formats)
    {
      object data = await content.GetDataAsync(format);
      // ただし、このdataは、GetTypeしてもSystem.__ComObject型が返ってきてしまい、
      // その実体が何であるか不明なものもある
    }

    // ローミングされてクリップボードに入れられたデータかどうか
    bool isFromRoamingClipboard = content.Properties.IsFromRoamingClipboard;
  }
}

データクラスを用意する

 上のようにして履歴アイテムのデータを取り出せます。けれど、そのためには非同期メソッドを呼び出さねばなりません。ということは、DataPackageView型のままでは、画面にデータバインディングできないわけです。

 そこで、画面にバインドするためのデータクラスを用意します。例えば次のコードのようなプロパティを持たせます(サンプルコードでは、AbstractClipboardDataクラスClipboardHistoryDataクラスに分かれています)。

画面にバインドするためのデータクラス(プロパティの部分)
public class ClipboardHistoryData
{
  // クリップボード履歴アイテムのデータ
  public ClipboardHistoryItem HistoryItem { get; private set; } // 元のオブジェクト
  public string Id => HistoryItem.Id; // 履歴ID
  public DateTimeOffset Timestamp => HistoryItem.Timestamp; // 履歴のタイムスタンプ

  // 表示用のデータ
  public string Text { get; private set; } // 履歴のテキストデータ
  public string TimestampTime => Timestamp.ToString("HH:mm:ss"); // タイムスタンプ(時分秒)
  // ……省略……
}

 このデータクラスに、DataPackageViewオブジェクトを与えてプロパティを設定するメソッドを用意しておきます(次のコード、AbstractClipboardDataクラス)。

画面にバインドするためのデータクラス(プロパティをセットする部分)
  protected async Task SetDataAsync(DataPackageView content)
  {
    // テキストデータを取り出してプロパティにセット
    this.Text = string.Empty;
    if (content.Contains(StandardDataFormats.Text))
      this.Text = await content.GetTextAsync();

    // ……省略……
  }

 ここではGetTextAsyncメソッドで取得したテキストをそのままプロパティに設定しています。実際に試したところ、テキストの場合は10MBytes以上のデータも履歴に入り、それをそのままUIのTextBlockコントロールにバインドすると(しかもそれが多数あると)メモリーを使いつくしてアプリが異常終了してしまいます。そこでサンプルコードのAbstractClipboardDataクラスでは、プロパティに保持するのはテキストデータの先頭1024文字までに限定しました。テキストデータの全体を必要とするシチュエーションが生じたときは、元のオブジェクトもプロパティにキャッシュしておいて必要に応じて再取得すればよいです(上のコードでは、そのためもあってClipboardHistoryItem型のHistoryItemプロパティを用意してあります)。

 また、次のコードのように、履歴アイテムを渡してこのデータクラスのインスタンスを生成する静的メソッドも作っておきます(ClipboardHistoryDataクラス)。データをセットするために非同期メソッドを呼び出しますから、コンストラクタで初期化する方法ではよろしくないのです。なお、上にも書いたように、受け取った履歴アイテムをHistoryItemプロパティにキャッシュしています。

画面にバインドするためのデータクラス(インスタンスを生成する部分)
  static async Task<ClipboardHistoryData> CreateNewDataAsync(ClipboardHistoryItem item)
  {
    var newItem = new ClipboardHistoryData()
    {
      HistoryItem = item,
    };
    await newItem.SetDataAsync(item.Content);
    return newItem;
  }

履歴リストを取得する(再)

 このデータクラスのコレクションを静的プロパティItemsとして用意し、また、データクラスのコンストラクタは外から呼べないようにしておきます(次のコード、ClipboardHistoryDataクラス)。

データクラスの唯一のコレクション
    public static ObservableCollection<ClipboardHistoryData> Items { get; }
      = new ObservableCollection<ClipboardHistoryData>();

    private ClipboardHistoryData()
    {
      // (avoid instance)
    }

 履歴リストを取得する流れは、先に説明した通りです。履歴リストを取得して上のデータクラスのコレクションを更新する静的メソッドは、次のコードのようになります(ClipboardHistoryDataクラス)。このメソッドを呼び出してやれば、データクラスのコレクションClipboardHistoryData.Itemsに最新の履歴データが入るというわけです。

履歴リストを取得してコレクションを更新する
    public static async Task<ClipboardHistoryItemsResultStatus> TryUpdateAsync()
    {
      if(!Clipboard.IsHistoryEnabled())
      {
        // 履歴がOFFになっている
        Items.Clear();
        return ClipboardHistoryItemsResultStatus.ClipboardHistoryDisabled;
      }

      var result = await Clipboard.GetHistoryItemsAsync();
      if (result.Status != ClipboardHistoryItemsResultStatus.Success)
      {
        // 履歴リストの取得に失敗した
        return result.Status;
      }

      // 履歴リストが取得できたので、コレクションを更新する
      Items.Clear();
      foreach (ClipboardHistoryItem item in result.Items)
        Items.Add(await CreateNewDataAsync(item));

      return ClipboardHistoryItemsResultStatus.Success;
    }

 ClipboardHistoryData.Itemsを画面にデータバインディングし、適切なタイミングでこのClipboardHistoryData.TryUpdateAsync静的メソッドを呼び出してやれば、履歴リストの内容が画面に表示されます。

 なお、以上のコードは従来のデスクトップアプリでもほぼ同じです。プラットフォームに依存するビットマップなどでは、DataPackageViewオブジェクトから取り出した後にデータバインディング用に加工する部分が異なってきます(WPFのAbstractClipboardDataクラスのSetDataAsyncメソッド)。

次のページ
UWPの制約に対処する

この記事は参考になりましたか?

UWPアプリ開発の最前線連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

HONDA R&Dで自動車の設計をやっていた機械屋さんが、技術の進化スピードに魅かれてプログラマーに。以来30年ほど、より良いコードをどうやったら作れるか、模索の人生。わんくま同盟の勉強会(名古屋)で、よく喋ってたりする。2014/10~2019/6 Microsoft MVP (Windows Devel...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/11229 2018/12/07 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー