注意:DataGridViewコントロールは、.NET Framework 2.0で新しく追加されました。
DataGridView.ClipboardCopyModeプロパティがDataGridViewClipboardCopyMode.Disable以外のときは、「Ctrl + C」キーを押すことにより、選択されたセルがクリップボードにコピーされるようになります。形式は、Text、UnicodeText、Html、CommaSeparatedValue(CSV)の4種類です。TextとUnicodeTextは、タブ区切り(TSV)形式のデータです。
ClipboardCopyModeによって、ヘッダーをコピーするかどうかを決めることができます。EnableAlwaysIncludeHeaderTextではヘッダーもコピーされ、EnableWithoutHeaderTextではコピーされません。既定値であるEnableWithAutoHeaderTextでは、ヘッダーが選択されている場合にコピーされます。
'ヘッダーをコピーしないようにする
DataGridView1.ClipboardCopyMode = _
DataGridViewClipboardCopyMode.EnableWithoutHeaderText
//ヘッダーをコピーしないようにする
DataGridView1.ClipboardCopyMode =
DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
ボタンをクリックした時や、メニューで選択した時にクリップボードにコピーするには、DataGridView.GetClipboardContentメソッドでDataObjectオブジェクトを取得して、これをClipboard.SetDataObjectメソッドに渡します。
以下に具体的なコードを示します。
'選択されたセルをクリップボードにコピーする
Clipboard.SetDataObject(DataGridView1.GetClipboardContent())
//選択されたセルをクリップボードにコピーする
Clipboard.SetDataObject(DataGridView1.GetClipboardContent());
上記のようにコピーすると、実はHTMLとCSVのデータに問題が発生します。HTML形式のデータは本来UTF8でなければなりませんが、ANSIになってしまいます。逆にCSVはANSIではなく、UTF8になってしまいます。私が試した限りでは、HTMLがUTF8にならない不具合は.NET Framework 4.0で修正されたようですが、CSVの方は直っていません。
上記の方法でコピーしたデータをExcelに貼り付けると、日本語が表示されなかったり、文字化けしたりします。これは、普通にExcelに貼り付けると、HTML形式のデータが使用されるためです。「形式を選択して貼り付け」で「Unicodeテキスト」か「テキスト」を選択すれば、正しく貼り付けることができます(「Csv」で貼り付けても、文字化けします)。.NET Framework 4.0からは、普通に貼り付けても文字化けしなくなったようです。
補足:HTML形式のデータは、文字コードの問題だけでなく、「What is this rogue version 1.0 of the HTML clipboard format? - MSDN Blogs」で指摘されているような、ヘッダが正しくない(バイト数で数えていない)という問題もあるようです。Excelの場合は、ヘッダが間違えていても文字コードがUTF8ならば正常に貼り付けることができるようですが、それ以外のアプリケーションではうまく行かない可能性もあります。HTML形式のデータを作成する方法については、「クリップボードにHTML形式のデータをコピーする」をご覧ください。
補足:「(VB.Net)DataObject.GetText()の引数にCSV(TextDataFormat.CommaSeparatedValu)指定時のバグ!? : 3流プログラマのメモ書き」によると、TSV(つまり、TextとUnicodeText)とCSVのデータは、フィールドに改行文字が含まれていてもダブルクォートで囲まないため、不正なデータになってしまうということです。その点では、HTML形式のデータは、改行が<br>に変換されるため、正常です。しかし、<br>で改行されたデータは、Excelに貼り付けると、セルが分割されてしまいます。それを防ぐためには、「br { mso-data-placement: same-cell; }」というスタイルが必要になります。
この問題の解決法としては、これらのデータを正しい文字コードでクリップボードにコピーすればよいということになります。その方法は、「クリップボードにHTML形式のデータをコピーする」と「クリップボードにCSV形式のデータをコピーする」で説明しています。
また、「DataGridView から Excel へコピーすると文字化けする | Moonmile Solutions Blog」によると、DataGridView.GetClipboardContentメソッドをオーバーライドして正しいデータを返すようにすれば、「Ctrl+C」キーでも正しくコピーできるようになるということです。
以下に、以上の問題を修正したDataGridViewの派生クラスの例を示します。DataGridViewの代わりに使用してください(詳しくは、「「○○○クラスの代わりに派生クラスを使用します」の意味は?」)。なお、文字コード以外の問題は、修正していません。
Imports System.Windows.Forms Imports System.IO Imports System.Text ''' <summary> ''' GetClipboardContentメソッドを修正したDataGridViewコントロール ''' </summary> Public Class DataGridViewEx Inherits DataGridView Public Overrides Function GetClipboardContent() As DataObject '元のDataObjectを取得する Dim oldData As DataObject = MyBase.GetClipboardContent() '新しいDataObjectを作成する Dim newData As New DataObject() 'テキスト形式のデータをセットする(UnicodeTextもセットされる) newData.SetData(DataFormats.Text, oldData.GetData(DataFormats.Text)) 'HTML形式のデータを取得する Dim htmlObj As Object = oldData.GetData(DataFormats.Html) Dim htmlStrm As MemoryStream = Nothing If TypeOf htmlObj Is String Then 'String型の時は、MemoryStreamに変換する htmlStrm = New MemoryStream( _ Encoding.UTF8.GetBytes(DirectCast(htmlObj, String))) ElseIf TypeOf htmlObj Is MemoryStream Then '.NET Framework 4.0以降では、MemoryStreamとなる htmlStrm = DirectCast(htmlObj, MemoryStream) End If If htmlStrm IsNot Nothing Then 'HTML形式のデータをセットする newData.SetData(DataFormats.Html, htmlStrm) End If 'CSV形式のデータを取得する Dim csvObj As Object = _ oldData.GetData(DataFormats.CommaSeparatedValue) Dim csvStrm As MemoryStream = Nothing If TypeOf csvObj Is String Then 'MemoryStreamに変換する csvStrm = New MemoryStream( _ Encoding.Default.GetBytes(DirectCast(csvObj, String))) ElseIf TypeOf csvObj Is MemoryStream Then '今のところこうなることはないが、将来を見据えて... csvStrm = DirectCast(csvObj, MemoryStream) End If If csvStrm IsNot Nothing Then 'CSV形式のデータをセットする newData.SetData(DataFormats.CommaSeparatedValue, csvStrm) End If Return newData End Function End Class
using System; using System.Windows.Forms; using System.IO; using System.Text; /// <summary> /// GetClipboardContentメソッドを修正したDataGridViewコントロール /// </summary> public class DataGridViewEx : DataGridView { public override DataObject GetClipboardContent() { //元のDataObjectを取得する DataObject oldData = base.GetClipboardContent(); //新しいDataObjectを作成する DataObject newData = new DataObject(); //テキスト形式のデータをセットする(UnicodeTextもセットされる) newData.SetData(DataFormats.Text, oldData.GetData(DataFormats.Text)); //HTML形式のデータを取得する object htmlObj = oldData.GetData(DataFormats.Html); MemoryStream htmlStrm = null; if (htmlObj is string) { //String型の時は、MemoryStreamに変換する htmlStrm = new MemoryStream( Encoding.UTF8.GetBytes((string)htmlObj)); } else if (htmlObj is MemoryStream) { //.NET Framework 4.0以降では、MemoryStreamとなる htmlStrm = (MemoryStream)htmlObj; } if (htmlStrm != null) { //HTML形式のデータをセットする newData.SetData(DataFormats.Html, htmlStrm); } //CSV形式のデータを取得する object csvObj = oldData.GetData(DataFormats.CommaSeparatedValue); MemoryStream csvStrm = null; if (csvObj is string) { //MemoryStreamに変換する csvStrm = new MemoryStream( Encoding.Default.GetBytes((string)csvObj)); } else if (csvObj is MemoryStream) { //今のところこうなることはないが、将来を見据えて... csvStrm = (MemoryStream)csvObj; } if (csvStrm != null) { //CSV形式のデータをセットする newData.SetData(DataFormats.CommaSeparatedValue, csvStrm); } return newData; } }
このようにクリップボードにコピーするのは簡単ですが、DataGridViewにペーストする(貼り付ける)のは大変です。「Ctrl + V」キーでペーストすることもできませんし、このような機能のメソッドが用意されている訳でもありません。つまり、自分でそのようなコードを書くしかありません。
以下に、ペーストを行うごく簡単な例を示します。この例では、コピーした行を現在のセルのある行以降にペーストするものです。行を挿入するのではなく、現在のセルの値を上書きしています(データがバインドされていると挿入できないため)。また、クリップボードにあるデータには列と行ヘッダーが含まれていると決め付けていますし、データ型も全く調べていません。
'現在のセルのある行から下にペーストする If DataGridView1.CurrentCell Is Nothing Then Return End If Dim insertRowIndex As Integer = DataGridView1.CurrentCell.RowIndex 'クリップボードの内容を取得して、行で分ける Dim pasteText As String = Clipboard.GetText() If String.IsNullOrEmpty(pasteText) Then Return End If pasteText = pasteText.Replace(vbCrLf, vbLf) pasteText = pasteText.Replace(vbCr, vbLf) pasteText = pasteText.TrimEnd(New Char() {vbLf}) Dim lines As String() = pasteText.Split(vbLf) Dim isHeader As Boolean = True For Each line As String In lines '列ヘッダーならば飛ばす If isHeader Then isHeader = False Else 'タブで分割 Dim vals As String() = line.Split(ControlChars.Tab) '列数が合っているか調べる If vals.Length - 1 <> DataGridView1.ColumnCount Then Throw New ApplicationException("列数が違います。") End If Dim row As DataGridViewRow = DataGridView1.Rows(insertRowIndex) 'ヘッダーを設定 row.HeaderCell.Value = vals(0) '各セルの値を設定 Dim i As Integer For i = 0 To row.Cells.Count - 1 row.Cells(i).Value = vals((i + 1)) Next i '次の行へ insertRowIndex += 1 End If Next line
//現在のセルのある行から下にペーストする if (DataGridView1.CurrentCell == null) return; int insertRowIndex = DataGridView1.CurrentCell.RowIndex; //クリップボードの内容を取得して、行で分ける string pasteText = Clipboard.GetText(); if (string.IsNullOrEmpty(pasteText)) return; pasteText = pasteText.Replace("\r\n", "\n"); pasteText = pasteText.Replace('\r', '\n'); pasteText = pasteText.TrimEnd(new char[] { '\n' }); string[] lines = pasteText.Split('\n'); bool isHeader = true; foreach (string line in lines) { //列ヘッダーならば飛ばす if (isHeader) { isHeader = false; continue; } //タブで分割 string[] vals = line.Split('\t'); //列数が合っているか調べる if (vals.Length - 1 != DataGridView1.ColumnCount) throw new ApplicationException("列数が違います。"); DataGridViewRow row = DataGridView1.Rows[insertRowIndex]; //ヘッダーを設定 row.HeaderCell.Value = vals[0]; //各セルの値を設定 for (int i = 0; i < row.Cells.Count; i++) { row.Cells[i].Value = vals[i + 1]; } //次の行へ insertRowIndex++; }
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。