Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(Windows 7 Professional : Excel 2016)
【初心者です】シート内の結合セルを解除→解除されたセルに元の値をコピー
投稿日時: 18/03/13 12:55:14
投稿者: つま

VBAを触り始めて数日ほどの初心者です。
よろしくお願いいたします。
 
 
シート内の全ての結合セルを結合解除し、
解除されたことによって空白になったセルに、結合解除前の値をコピーする。
 
というVBAを作ろうとしてまして、
 
拾い物ですが、
 
Sub ○○()
    Dim H As Range
    Dim K As Range
    Dim A As Variant
    For Each H In ActiveSheet.UsedRange
        If H.MergeCells Then
            Set K = H.MergeArea
            A = H
            H.UnMerge
            K = A            
        End If
    Next
End Sub
 
というもので目的を満たすことはできました。
 
 
しかし、このVBAが一行ごとにどのような動作をしているのかいまいち理解できておらず、
このままではVBAの知識が全く身につかないと思い投稿させていただきました。
特に7行目からの変数の格納あたりから全くついていけていません。
 
一行ごとにどのような動作をしているのかご教授いただけますと幸いです。
また、上記のコードよりもスマートなものがあれば、それも合わせてご指南ください。
 
よろしくお願いいたします。

回答
投稿日時: 18/03/13 13:33:04
投稿者: mattuwan44

Sub test()
    '変数の宣言
    Dim H As Range
    Dim K As Range
    Dim A As Variant
     
    'アクティブなシートの使っているセル範囲の各セルを巡回
    For Each H In ActiveSheet.UsedRange
        'もしセルが結合されていたらその時は、、、
        If H.MergeCells Then
            '一旦、そのセルを含む結合されたセル範囲を変数に記録
            Set K = H.MergeArea
            '一旦、値も記録
            A = H
            'セルの結合を解除
            H.UnMerge
            'セル範囲に値を一括で代入
            K = A
        End If
    Next
End Sub
 
これでわかりますかね?
 
 
プロパティが省略されていて、文字量は減るけど、
読んで何しているか一瞬分かり難いですよね、
 
僕ならこう書くかな。
 
Sub test2()
    Dim c As Range
    Dim rng As Range
 
    For Each c In ActiveSheet.UsedRange
        If c.MergeCells Then
            Set rng = c.MergeArea.Cells '結合セルのセル範囲を変数に代入
            rng.UnMerge
            rng.Value = rng(1).Value 'セル範囲の1番目のセルの値をセル範囲全体に代入
        End If
    Next
End Sub
 
やってることは1個変数を減らしただけで変わらないですね^^;
 
う〜ん。どちらも1回処理したセルも重複してみてるから効率悪いかも。。。
処理したセル範囲は記録しておいて、、、
あぁ、けど1行余分にIF文書いてたいした効率化は出来ないのかなぁ。。。
 
先に結合セルだけ検索してから、
処理したら。。。。まぁ、あんまり頑張れる要素はないですかねー。。。

投稿日時: 18/03/13 14:06:33
投稿者: つま

>> mattuwan44さま
 
ありがとうございます!
とてもわかりやすいご解説でした。
 
元々のコードとご紹介いただいたものでは、
残念ながら手元のブックの処理にかかる時間はほぼ変わらずでした。。
ただ、ご紹介いただいたものの方が処理してる内容はわかりやすく感じますね。
 
まだ少し理解しきれていない点がありまして、
元のコードの、
 
'一旦、値も記録
 A = H
 
の部分なのですが、
ここに関わる行を省略&修正して
 
Sub test()
 
    Dim H As Range
    Dim K As Range
    For Each H In ActiveSheet.UsedRange
        If H.MergeCells Then
            Set K = H.MergeArea
            H.UnMerge
            K = H
        End If
    Next
  
 End Sub
 
としてしまっても、動作自体は変わらず処理されてしまいます。(処理時間は5%ほど早くなった)
こうしてはいけない理由というのはRange型とVariant型の違いあたりが関係しているのでしょうか?
 
重ね重ねの質問で申し訳ありませんが、何卒よろしくお願いいたします。

投稿日時: 18/03/13 14:22:56
投稿者: つま

重ね重ね申し訳ありません。
 
Set K = H.MergeArea
 

 
Set rng = c.MergeArea.Cells
 
の意味の違いも、もう少し詳しくご教授いただけると幸いです。
(.Cellsの有り無しでどう違うか、ということです)
 
よろしくお願いします。

回答
投稿日時: 18/03/13 14:45:23
投稿者: WinArrow
投稿者のウェブサイトに移動

つま さんの引用:
重ね重ね申し訳ありません。
 
Set K = H.MergeArea
 

 
Set rng = c.MergeArea.Cells
 
の意味の違いも、もう少し詳しくご教授いただけると幸いです。
(.Cellsの有り無しでどう違うか、ということです)
 
よろしくお願いします。

 
どちらも同じですよ
Set K = H.MergeArea
Debug.Print K.Address
Set rng = c.MergeArea.Cells
Debug.Print Rng.Address
コードを読んだときにどちらが可読性があるかということでは・・・

回答
投稿日時: 18/03/13 14:59:49
投稿者: もこな2

面白そうなテーマだったので、極力変数使わずに書いてみました。
(単にWithステートメントに置き換えたとも言う・・・)

Sub Sample()
'==変数の宣言とか
    Dim MyRNG As Range

'==処理
    For Each MyRNG In ActiveSheet.UsedRange
        If MyRNG.MergeCells Then
            With MyRNG.MergeArea
                .UnMerge '解除する
                .Value = .Item(1).Value 'セル範囲1番目の値をセル範囲全部に与える
            End With
        End If
    Next MyRNG
End Sub

投稿日時: 18/03/13 15:21:05
投稿者: つま

>> WinArrowさま
 
回答ありがとうございます!
 
この2つは同じ内容なのですね。
Debug.Printも知らなかったので、勉強になりました。。
 
 
>> もこな2さま
 
回答ありがとうございます!
 
withステートメントを使えば、1つの変数で複数の指示を与えられるのですね!
右も左もわからない私でも、このコードはすっと頭で理解できました。。
 
「.Item(1)」の部分は、この場合「.Cells(1)」でも同義ですか?
「.Item(1)」のほうが汎用性が高そうなので、このままのほうが良さそうですかね。。

回答
投稿日時: 18/03/13 17:08:34
投稿者: WinArrow
投稿者のウェブサイトに移動

>Debug.Print
は、デバッグツールの一つです。
 
デバッグツールとしては
・ステップ実行
・ブレークポイント
というのもあります。
 
「F8」キーを押しながら、コードを1行づつ実行し、
その時点の変数の値や流れを検証することができます。
 
プログラムの完成度が高めるために、習得しましょう。
 

回答
投稿日時: 18/03/13 18:08:58
投稿者: mattuwan44

>(.Cellsの有り無しでどう違うか、ということです)
全ては可読性の問題です。
 
>特に7行目からの変数の格納あたりから全くついていけていません。
というコメントがあったので、
変数に何を代入しようとしているかが解らなくなっていると推測しました。
 
つまり、セルを代入しているんだ。値を代入するんだ。という意思を文章の中で明示すれば、
迷うことがなくなるかなと思い、敢えて書きました。
半年後、1年後の自分は他人と同じですぞ。
今わかったとしても1年後分かり難いと感じるかも知れません。
少しでもわかりやすくならないかをまずは考えてです。
 
>こうしてはいけない理由というのはRange型とVariant型の違いあたりが関係しているのでしょうか?
省略が許されているのだから、してはいけないということはありません。
が、後で自分がデバッグで苦労しない文章を書けるようになるべきです。
 
> A = H
 >   K = A

 
これだけを見て何をしているかよくわかりませんよね。
 
rng.Value = rng(1).Value
 
rngという変数名でRangeオブジェクトを入れてある変数と類推できるようにし、
Valueプロパティを明示することで値を移し替えていることを明示してます。
これを冗舌なものいいと思わないか、
それとも省略できるものは省略すればいいと思うかは個人(または開発チーム)の判断です。
 
参考URL>>
http://www.excel.studio-kazu.jp/kw/20180221163017.html
 
こういった議論はここのサイトでも行われたと思いますが、
過去ログが半年で消されてしまうので、誰かがログを取ってくれてたら別ですが、
検索できないのが残念なところです。
 
>としてしまっても、動作自体は変わらず処理されてしまいます。(処理時間は5%ほど早くなった)
それは高速化のテクニックとして有用かもです。
.valueを付けるか付けないかでもたくさんループすれば速度差が出てくるでしょう。
オフィス田中さんのサイトで実験結果が出てると思いますし、
自分でも試すことは有用だろうとは思いますが、
個人的に何万回もループすることがほぼないので、高速化より
1年後の自分への手紙的要素が強い書き方を目指してますので、そこはご了承願います。
 
>「.Item(1)」の部分は、この場合「.Cells(1)」でも同義ですか?
正確には、Rangeオブジェクト.Item(1)か、
Rangeオブジェクト.Cells.Item(1)です。
Cellsプロパティには実は引数が無いので注意が必要です。
>「.Item(1)」のほうが汎用性が高そうなので、このままのほうが良さそうですかね。。
RangeオブジェクトにItemプロパティを使う場合は、注意が必要です。
かっこつけて使ってると痛い目を見ることがあります。
多分、.Cells(1)の方が間違いがないので、個人的にはこちらを多用してます。
まぁ、不都合が合って苦労した方が覚えると思うので、
いろいろやってみてください。

投稿日時: 18/03/13 18:43:42
投稿者: つま

>> WinArrowさま
 
デバッグツールの習得が大切というのは手元の書籍等にも書いてありますね。。
精進いたします!
 
 
>> mattuwan44さま
 
とても細かく教えてくださり、何とお礼を申し上げればよいか…。
 

引用:
>特に7行目からの変数の格納あたりから全くついていけていません。
というコメントがあったので、
変数に何を代入しようとしているかが解らなくなっていると推測しました。

まさに仰る通りです。
可読性の高いコードを書くというのが大切なのだと痛感しました。。
 
貼っていただいたリンク先を覗いてみましたが、
恥ずかしながら今の私には内容がほとんどわかりませんでした;;
徐々に理解できるようになるよう努力します!
 
 
今回のトピックについては一旦〆させていただきます!
ご回答いただきました皆さま、誠にありがとうございます。