2012年3月20日火曜日

(C#, VB.NET)DataGridView から Excel への Copy&Paste で文字化け

DataGridView はプロパティの ClipboardCopyMode が Disable 以外に設定されていると表示されたセルの内容を、そのままクリップボードにコピーする事が可能です。とても便利な機能です。

ところがペースト(貼り付け)先が Excel(エクセル) だと、そのままペーストすると何故か文字化けが起きてしまいます。※未確認ですが Excel2010 では発生しないという情報もありました。

ペーストする時に『形式を選択して貼り付け』で『テキスト』を選択すれば文字化けしませんが、ユーザー一人一人にお願いするのは面倒です。これをコードから行えないものかと色々と調べてみました。

文字化けの原因は Excel がペースト時にクリップボードの内容を HTML Format として解釈してしまうかららしいです。他にも、文字コードの扱いが Excel(Shift-JIS) と DataGridView(Unicode) で異なる、という情報もありましたが、こちらについての真偽は不明です。

DataGridView.GetClipboardContent().GetFormats() でクリップボードに渡されるデータの種類を調べたら Csv, HTML Format, UnicodeText, Text という4つで構成されていました。これを Text だけにしてやれば、文字化けは起きなくなるはずです。

そもそも DataGridView はどうやってコピーのタイミング(DataGridView からクリップボードにデータが渡される)を捕捉しているのでしょう。ソースが見たいです…しかし、私は Visual Studio 2005 ユーザー。

MSDN で GetClipboardContent メソッドを調べてみるとページの最後の方に
継承時の注意 カスタマイズされたクリップボード値を指定するには、このメソッドをオーバーライドします。たとえば、このメソッドをオーバーライドして、カスタムのセル型からの値のコピーをサポートできます。
との事でした。

その他にも Ctrl+C をフックしたり、コンテキストメニュー(右クリックで表示)のショートカットを利用して Ctrl+C を横取りするという情報もありました。

オーバーライドは既存画面の修正が楽です。ユーザーへの分り易さを考えたらコンテキストメニューです。それぞれ一長一短がありますね。

今回は…
GetClipboardContent() メソッドをオーバーライドした、カスタムコントロールを追加作成し既存画面の DataGridView を置き換える事にしました。各画面には『Ctrl+C でコピー可能』とラベル表記するという事で。
次期メジャーバージョンアップの時はコンテキストメニューを組み合わせた方法で対応しようと思います。

■C#
namespace SampleApplication
{
    public partial class DataGridViewSa : DataGridView
    {
        public override DataObject GetClipboardContent()
        {
            DataObject src = base.GetClipboardContent();

            if (src == null) return null;

            string t = src.GetText(TextDataFormat.Text);

            DataObject res = new DataObject(DataFormats.Text, t);
            return res;
        }
    }
}

■VB.NET
Public Class DataGridViewSa
    Inherits System.Windows.Forms.DataGridView

    Public Overrides Function GetClipboardContent() As System.Windows.Forms.DataObject
        Dim src As DataObject = MyBase.GetClipboardContent()

        If src Is Nothing Then Return Nothing;

        Dim t As String = src.GetText(TextDataFormat.Text)

        Dim res As DataObject = New DataObject(DataFormats.Text, t)
        Return res
    End Function
End Class

■環境
OS:Microsoft Windows XP Home Edition 日本語 Service Pack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2