DataGridのコピー機能が、日本語限定でバグってる(四)

コピー元情報をもとに、クリップボードへ文字列を設定する。 とりあえず、Text形式とHTML形式だけサポートする。
Text形式は元のままを使えば良いし、HTML形式も、一部(バイト数なのに文字数を渡している)箇所を修正すればいい。
DataGridからコピーする際のイベントデータ用クラスとして、DataGridRowClipboardEventArgsクラスがあるので、これを使う。
コピー用メソッド本体。

//namespaceは割愛
public static class WpfHelper {

/// <summary>
/// ApplicationCommands.Copyの実行用<br/>
/// 選択セルの内容をクリップボードに設定します。<br/>
/// </summary>
/// <param name="sender">対象オブジェクト(DataGrid)</param>
/// <param name="e">イベントデータ</param>
private static void ExecuteCopyingData(object sender, ExecutedRoutedEventArgs e) {

var dataGrid = sender as DataGrid;
var selectedRange = dataGrid.GetSelectedRange();

if (selectedRange.SelectedItems.Count == 0) {
return;
}

//クリップボードに設定する文字列を生成します。
//HTMLとテキストをサポートさせます。
var htmlContent = new System.Text.StringBuilder();
var textContent = new System.Text.StringBuilder();

//クリップボードへのコピーの際、列ヘッダを含める。
if (dataGrid.ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader) {
var args = new DataGridRowClipboardEventArgs(
null, selectedRange.MinColIndex, selectedRange.MaxColIndex, true);
WpfHelper.OnCopyingRowClipboardContent(
dataGrid, selectedRange.ColIndexList, args);
htmlContent.Append(args.FormatClipboardCellValues(DataFormats.Html));
textContent.Append(args.FormatClipboardCellValues(DataFormats.Text));
}

foreach (var item in selectedRange.SelectedItems.OrderBy(item => item.Key)) {
var args = new DataGridRowClipboardEventArgs(
item, selectedRange.MinColIndex, selectedRange.MaxColIndex, false);
//イベントデータにコピー対象情報を設定します。
//イベントデータクラスが生成用のメソッドを持っています。
WpfHelper.OnCopyingRowClipboardContent(
dataGrid, selectedRange.ColIndexList, args);
htmlContent.Append(args.FormatClipboardCellValues(DataFormats.Html));
textContent.Append(args.FormatClipboardCellValues(DataFormats.Text));
}

//HTML形式専用(ここが本稿のキモ)
//HTMLフォーマットとして必要なものを前後に付与します。
//オリジナルのコピー機能は、このメソッドの内部ロジックがおかしいのです。
WpfHelper.GetClipboardContentForHtml(htmlContent);

//とりあえず、Text形式とHTML形式だけサポート
var dataObject = new DataObject();
dataObject.SetData(DataFormats.Html, htmlContent.ToString(), true);
dataObject.SetData(DataFormats.Text, textContent.ToString(), true);

Clipboard.SetDataObject(dataObject, true);
return;
}
}

イベントデータに設定するメソッド

public static class WpfHelper {

/// <summary>
/// コピー時に、クリップボードに設定するセルの値と位置情報をイベントデータに設定します。
/// </summary>
/// <param name="dataGrid">コピー元のDataGrid</param>
/// <param name="selectedColIndex">選択された列表示インデックスのセット</param>
/// <param name="args">イベントデータ(設定先)</param>
private static void OnCopyingRowClipboardContent(DataGrid dataGrid,
IList<int> selectedColIndex,
DataGridRowClipboardEventArgs args) {

foreach (var colIndex in selectedColIndex.OrderBy(idx => idx)) {
DataGridColumn column = dataGrid.ColumnFromDisplayIndex(colIndex);

if (column.Visibility != Visibility.Visible) {
//非表示列は無視します。
continue;
}

if (args.IsColumnHeadersRow) {
args.ClipboardRowContent.Add(
new DataGridClipboardCellContent(
args.Item, column, column.Header));
} else {
object cellValue = column.OnCopyingCellClipboardContent(args.Item);
args.ClipboardRowContent.Add(
new DataGridClipboardCellContent(
args.Item, column, cellValue));
}
}

return;
}
}

これが肝心のHTML形式を構築する箇所。

public static class WpfHelper {

/// <summary>HTML書式の開始タグセット</summary>
private static readonly string HTML_START_FRAGMENT =
"<HTML>\r\n<BODY>\r\n<!--StartFragment-->";

/// <summary>HTML書式の終了タグセット</summary>
private static readonly string HTML_END_FRAGMENT =
"\r\n<!--EndFragment-->\r\n</BODY>\r\n</HTML>";

/// <summary>HTML書式のprefix文字列(テンプレート)</summary>
private static readonly string HTML_PREFIX =
"Version:1.0\r\nStartHTML:00000097\r\nEndHTML:{0}\r\n" +
"StartFragment:00000133\r\nEndFragment:{1}\r\n";

/// <summary>バイト数部分の書式(00000000)</summary>
private static readonly string BYTE_COUNT_FORMAT = "00000000";

/// <summary>
/// クリップボードへHTML形式で設定する為に必要な文字列を前後に付与します。<br/>
/// ただし、表形式扱いになるので、引数の中身は、<TR>や<TD>を自前で設定しておいてください。<br/>
/// </summary>
/// <param name="content">対象文字列</param>
private static void GetClipboardContentForHtml(StringBuilder content) {

if (content == null) {
return;
}

content.Insert(0, "<TABLE>");
content.Append("</TABLE>");

//note:C#上の文字列は基本Unicodeなので、Unicodeでのバイト数をカウントする。
// 本家DataGridは、content.Lengthを渡している。
//note:Unicodeではなく、UTF8のバイト数カウントを渡す。
// Unicodeでは、Outlookへの貼り付けができない。
var byteCount = Encoding.UTF8.GetByteCount(content.ToString());

//var byteCount = content.Length;
//↓の"+10"は、実際の文字列とのoffset値。{0}と{1}が、8桁の数字に変わるため。
//EndOfFragmentは、</Table>までのバイト数
int bytecountEndOfFragment = (HTML_PREFIX.Length + 10) +
HTML_START_FRAGMENT.Length + byteCount;

//EndOfHtmlは、全体のバイト数
int bytecountEndOfHtml =
bytecountEndOfFragment + HTML_END_FRAGMENT.Length;

//前後に付与
string prefix = string.Format(
CultureInfo.InvariantCulture,
HTML_PREFIX,
bytecountEndOfHtml.ToString(BYTE_COUNT_FORMAT,
CultureInfo.InvariantCulture),
bytecountEndOfFragment.ToString(BYTE_COUNT_FORMAT,
CultureInfo.InvariantCulture)) +
HTML_START_FRAGMENT;
content.Insert(0, prefix);
content.Append(HTML_END_FRAGMENT);
return;
}
}
何とか一通り書けたか。でも、一か所のためだけに、かなりの量をコーディングするのって、なんだかなぁという感じである。MSに報告したほうが早いのか?でも、「次期バージョンで対応します」とか言われそう。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m