和風スパゲティのレシピ

日本語でコーディングするExcelVBA

セル範囲の値だけをコピーする - Range.Value

セルの値だけを、範囲から範囲へコピーする方法を紹介します。

目的のセル範囲

↑の画像のように、「A1:C3の正方形を、D4:F6に値だけコピー」しましょう。


これを実行するコードは、

Range("D4:F6").Value = Range("A1:C3").Value

以上です。



え?これだけ?

って最初は思いますよね。


「値だけをコピー」したい場合は、これだけでOKです。



なお、Range(Cells,Cells)の書き方を使ってセル範囲を指定する場合も、
同じ書き方でOKです。

Range(Cells(4, 4), Cells(6, 6)).Value = Range(Cells(1, 1), Cells(3, 3)).Value

 
シート・ブックが異なる場合でもこの書き方でOKです。

Worksheets("データ1").Range("D4:F6").Value _
    = Worksheets("データ2").Range("A1:C3").Value

 

注意点として、セル範囲の場合は単独セルの時と違い、
「.Value」を省略できません。(省略すると違う意味になります)


普段は省略形の「Range("A1")」と書いてRange("A1").Valueを扱っていると、
わかっていても忘れたりするので注意してください。

 

真っ先に思いつく「値貼り付け」

↑の「.Value」はRangeオブジェクトの知識がないと思いつくわけがないので、
多くの人は真っ先に「コピー ⇒ 値貼り付け」を思いついたと思います。

そのコードはこれになります。

Range("A1:C3").Copy
Range("D4").PasteSpecial Paste:=xlPasteValues

 
マクロ記録流用派プログラマの方は、

Range("A1:C3").Select
Selection.Copy
Range("D4").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
    :=False, Transpose:=False

こんなコードを使っているかもしれません。


このことについて、かの有名なOffice TANAKAの田中様が、
Office TANAKA - セルの操作[セルのコピー]の記事にて、
↓このような見解を示しておられます。


「値だけコピーするんだから・・・そうか!形式を選択して貼り付けの"値"を使えばいいのか。うん、手作業でもそうするしな。よし、マクロ記録してみよう。え~と、なになに・・・PasteSpecial・・・なんか、ゴチャゴチャして・・・ま、いっか。マクロ記録されたんだから正しいんだろう。これをそのまま使っちゃえ」などと考えて、ずらずらとPasteSpecialを繰り返している人が、実に多いです。ほら、これを読んでいるあなた。あなたですよ(笑)




大丈夫です。

私も初めてこの記事を読んだとき、図星でした。
多分みんなそうだから、心配しないでください。

今日から「Range.Vlaue = Range.Value」の形で書けばOKです。


※ なお、PasteSpecialとValueの比較ですが、
 コードの読みやすさ、書きやすさはもちろんのこと、
 速度面でも、検証する気も起きないくらい差があります。

Valueを使うにはペースト先の範囲を取得する必要がある

↑といいつつ、マクロ記録もそこまで馬鹿には出来ません。

ペースト先がRange("D4")で済んでいる点は、Value法より優れています。


Range.Value=Range.Valueの方法を使うときは、
ペースト先の範囲とコピー元の範囲が合っていないといけないので、
Range("D4:F6")の「F6」を調べる必要が出てしまいます。


これをいちいちやるのは面倒ですから、
汎用関数(Subプロシージャ)を作って自動化してしまいましょう。

' エリア⇒エリアの値のコピーを関数化
Sub 値をコピーする(コピー元エリア As Range, ペースト基準セル As Range)
    ペースト基準セル.Resize(コピー元エリア.Rows.Count _
                                 , コピー元エリア.Columns.Count).Value = コピー元エリア.Value
End Sub
' 使い方
Call 値をコピーする(Range("A1:C3"), Range("D4"))

キレイになりましたね。


Subプロシージャの中身は単純です。

ペースト基準セルを、Resize(コピー元の行数,コピー元の列数)によって、
コピー元エリアと同じ大きさにしています。


この関数を1度作って(このページからコピぺして)しまえば、
あとは使い方のように書くだけになります。


「Sub/Functionプロシージャ作成は複雑な処理のためのもの」
と無意識に思っている方も多いみたいですが、

むしろこういう「ただ読みやすくするための関数」の方が、
単純で理解しやすく、使い勝手も良かったりしますのでおすすめですよ。

おまけ:もっと便利なプロシージャに

汎用関数の別パターンとしては、
コピー元のエリアも第1セルを基準点として渡し、
プラスして高さと幅も引数で指定させる方法があります。

Sub 値をコピーする(コピー元基準セル As Range, ペースト基準セル As Range _
    , エリア高 As Long, エリア幅 As Long)

    ペースト基準セル.Resize(エリア高, エリア幅).Value _
        = コピー元基準セル.Resize(エリア高, エリア幅).Value
End Sub

 
今回の例でいうと、

Call 値をコピーする(Range("A1"), Range("D4"), 3, 3)

こんな使い方になります。


表形式のデータシートなんかだと、
「C3だとはわからないけど、3×3の正方形だとわかる」
ような状況は多いですからね。

このパターンも便利です。


さらに少しレベルは上がりますが、
↑の2つをどちらもこなせる関数にもできます。

高さと幅の引数があるが、省略した場合はコピーエリアの高さと幅を取る

という方法です。

Sub 値をコピーする(コピー元基準セルまたはエリア As Range, ペースト基準セル As Range _
    , Optional ByVal エリア高 As Long = -1, Optional ByVal エリア幅 As Long = -1)
    
    ' ◇ エリアサイズの省略時は、コピーエリアのサイズを取得
    If エリア高 = -1 Then エリア高 = コピー元基準セルまたはエリア.Rows.Count
    If エリア幅 = -1 Then エリア幅 = コピー元基準セルまたはエリア.Columns.Count
    
    ' 値をコピー
    ペースト基準セル.Resize(エリア高, エリア幅).Value = コピー元基準セルまたはエリア.Resize(エリア高, エリア幅).Value

End Sub

これが一番おすすめです。


高さと幅を「Optionalキーワードを使って省略可能な引数に」しておき、
省略時(-1だったとき)は、コピー元エリアのサイズを取得する仕組みです。


この関数であれば、

Call 値をコピーする(Range("A1:C3"), Range("D4"))

Call 値をコピーする(Range("A1"), Range("D4"), 3, 3)

このどちらの書き方でも同じ処理になりますので、状況に合わせて使い分けることができます。


レベルが上がるといってもコピペするだけなら労力は変わりませんので
持っていくならこちらをどうぞ。



ちなみに

「この程度の処理をわざわざSubプロシージャにしなくてもいいんじゃないの?」

と思った方へ。
 

ペースト基準セル.Resize(コピー元エリア.Rows.Count _
                             , コピー元エリア.Columns.Count).Value = コピー元エリア.Value

この式中3回も出てくるコピー元エリアを、
よくある、Range(Cells(R1, C1), Cells(R2, C2))で書くとしましょう。

このとき、メインコードで値貼り付けのコードをそのまま書く場合は、

Cells(R3, C3).Resize(Range(Cells(R1, C1), Cells(R2, C2)).Rows.Count _
                            , Range(Cells(R1, C2), Cells(R1, C2)).Columns.Count).Value _
    = Range(Cells(R1, C1), Cells(R2, C2)).Value

こうなります。


わけわかんないですし、
よく見るとバグがありますよ?


まあ行番号と列番号がわかっているので、

Cells(R3, C3).Resize(R2 - R1, C2 - C1).Value _
    = Range(Cells(R1, C1), Cells(R2, C2)).Value

こうやって引き算を使ったResizeもできますけどね。


でもよく見るとバグがありますよ?


計算式でコードを短くするのは、
「R2-R1」と「R2-R1+1」のどちらが正しいか?
に即答できるくらいでないと危険です。

今回は「距離」ではなく「要素数」なので、「+1」が必要です。
「R2-R1」は、まさに距離である「Offsetの引数」に使うときですね。


いずれにせよ、関数にした方がはるかに見やすいコードです。

Call 値をコピーする(Range(Cells(R1, C1), Cells(R2, C2)), Cells(R3, C3))

 

先ほども書きましたが、

「プロシージャ分割は複雑な処理のためのもの」ではなく、
「書くのが面倒なコードを短縮するためのもの」であってもよいのです。

理解しやすく、しつこいコードを書きやすいってだけではなく、
いらぬバグを書く心配もなくなります。


というか、こういう短いプロシージャを作りまくって慣れて初めて、
複雑な処理のための、高度なプロシージャを作れるようになると思います。

訓練もかねて、じゃんじゃんSub/Functionプロシージャを作っていきましょう。