クリップボード履歴を取得する
それでは実際に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メソッド)。
