グループ化した画像やオートシェイプをShape.Name指定でピックアップして所望のブックに立て続けにコピペする【ExcelVBA】

## 概要

テーブルにリストアップしたシェイプ名を

|Shapeまとめwb名|Shapeまとめws名|対象Shape名|貼り付け先wb名|貼り付け先ws名|
|---|---|---|---|---|
|画像まとめブック.xlsx|Sh1|**Gr2**|貼り付け先ブック.xlsx|Sheet2|
|画像まとめブック.xlsx|Sh1|**Gr1**|貼り付け先ブック.xlsx|Sheet1|
|画像まとめブック.xlsx|Sh2|**Gr2**|貼り付け先ブック.xlsx|Sheet1|

画像とかオートシェイプをまとめたブックから探してきて
（あらかじめシェイプやグループに名前を付けておく必要あり）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281933/67172587-0ebb-4c61-941d-74af3e50366e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281933/7dfbbe0e-a873-bf07-a65c-52e6a944f854.png)


対象ブックにグループごと転記する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281933/640a66cb-1703-1482-6a0f-95263febf959.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281933/14239411-0953-d7e4-aae6-7055b3ecf19f.png)


というもの

## 注意点 
### 同名シェイプについて
ExcelもPowerPointも同じ名前のシェイプが存在しても良いことになっているので、転記元も転記先も同じ名前のシェイプがあると思ってたんと違う動きになります。

転記先は基本的に新規作成を前提で作っているので無視するとして、転記元ブックのシェイプ名管理は量が増えてくると別で必要かも。

### 縦に追加して貼り付けていく仕様です
縦に連続で貼り付けていくようにしてあるので、所定の位置にしたいとか、既存のシェイプの間に入れたいとかは別途調整必要です。


## コード
``` vb
Option Explicit

Const wb名画像まとめブック = "画像まとめブック.xlsx"
Const wb名貼り付け先ブック = "貼り付け先ブック.xlsx"
Const tblName = "テーブル1"
Const PosLeft = 100
Const Pos縦隙間 = 10

Private Enum c
  cShpまとめwb名 = 1
  cShpまとめws名
  c対象Shape名
  c貼り付け先wb名
  c貼り付け先ws名
End Enum

Private Type Property_
  pShpまとめwb名 As String
  pShpまとめwb As Workbook
  
  pShpまとめws名 As String
  pShpまとめws As Worksheet
  
  p対象Shape名 As String
  p対象Shape As Object
  
  p貼り付け先wb名 As String
  p貼り付け先wb As Workbook
  
  p貼り付け先ws名 As String
  p貼り付け先ws As Worksheet
End Type
Dim Arr() As Property_

Sub 別ブックから画像を持ってくる()
  Application.ScreenUpdating = False
  Call Set下ごしらえ
  
  Dim i As Long
  Dim ws As Worksheet
  Dim PosTop As Double
  For i = 1 To UBound(Arr)
    Set ws = Arr(i).p貼り付け先ws
    With ws
      '貼り付け
      PosTop = シート内の際下端シェイプの下端座標(ws) + Pos縦隙間 '貼り付け前に既存のシェイプ位置を取得しておく
      Arr(i).p対象Shape.Copy
      .Paste Destination:=.Cells(1, 1)
      
      '貼り付けた図の位置決め
      With .Shapes(Arr(i).p対象Shape.Name)
        .Left = PosLeft
        .Top = PosTop
      End With
      
    End With
  Next
  Application.ScreenUpdating = True
End Sub

Private Sub Set下ごしらえ()
  Dim myTable As ListObject: Set myTable = Range(tblName).ListObject
  Dim i, cnt
  Dim tblDB As Range: Set tblDB = myTable.DataBodyRange
  
  cnt = tblDB.Rows.Count
  ReDim Arr(cnt)
  
  For i = 1 To cnt
    With Arr(i)
      .pShpまとめwb名 = tblDB(i, c.cShpまとめwb名)
      .pShpまとめwb名 = tblDB(i, c.cShpまとめwb名)
      .pShpまとめws名 = tblDB(i, c.cShpまとめws名)
      .p対象Shape名 = tblDB(i, c.c対象Shape名)
      .p貼り付け先wb名 = tblDB(i, c.c貼り付け先wb名)
      .p貼り付け先ws名 = tblDB(i, c.c貼り付け先ws名)
      
      Set .pShpまとめwb = Workbooks(.pShpまとめwb名)
      Set .pShpまとめws = .pShpまとめwb.Sheets(.pShpまとめws名)
      Set .p対象Shape = .pShpまとめws.Shapes(.p対象Shape名)
      Set .p貼り付け先wb = Workbooks(.p貼り付け先wb名)
      Set .p貼り付け先ws = .p貼り付け先wb.Sheets(.p貼り付け先ws名)
    End With
  Next
  
End Sub

Private Function シート内の際下端シェイプの下端座標(ws As Worksheet) As Double
  Dim shp As Shape
  Dim shp暫定 As Shape
  
  If Is指定wsにShapeが存在する(ws) Then
    Set shp暫定 = ws.Shapes(1) '仮で1番目のシェイプを指定
    For Each shp In ws.Shapes
      With shp
        If .Top > shp暫定.Top Then
          Set shp暫定 = shp
        End If
      End With
    Next
    シート内の際下端シェイプの下端座標 = shp暫定.Top + shp暫定.Height
  Else
    シート内の際下端シェイプの下端座標 = 0
  End If
  
End Function

Private Function Is指定wsにShapeが存在する(ws As Worksheet) As Boolean
  If ws.Shapes.Count = 0 Then
    Is指定wsにShapeが存在する = False
  Else
    Is指定wsにShapeが存在する = True
  End If
End Function
```

## 残課題
### シェイプ追加時に印刷範囲を超えるとシートを追加する機能

Excelを印刷物に使うのは嫌いなのですが、社内の文書フォーマットがExcelになっており、かつフッターのページ番号がただの連番ではなく、親ページ番号＋枝番、表紙と目次にはページ番号はつけないという面倒な書式になっているので下記リンクのような瞬殺技が使えませんorz...

https://forest.watch.impress.co.jp/docs/serial/exceltips/1190939.html

最終的には以前作成したの各シートのフッターを一括編集するマクロを使うにしても、あくまでシートごとにしか付与できないのでシートが縦に長い状態だと対応できません。

https://qiita.com/taleau/items/d3da8a09c1d12bdd2e62

なので非常に面倒ですがシェイプ追加時に印刷範囲を超えるとシートを追加する必要が出てきます。

正直、このあたりの書式を変える方がはるかに効率改善になるし、属人化問題の初期消火どころかボヤすら発生させない予防策になりますよ。

なのでこの残課題に着手する日を回避することから始めます。


## 参考

``` vb:Sub_別ブックから画像を持ってくる()
Arr(i).p対象Shape.Copy
.Paste Destination:=.Cells(1, 1)
```
`Copy`,`Paste`メソッドはあまり使いたくなかったのですが、

真髄さんでさえ使っていたのでしゃーないか。まずは動けばいいしね。

https://excel-ubara.com/excelvba5/EXCEL113.html

ブック上ではなく画像ファイルを直接呼ぶ方法もありますが、今回の用途はグループ化したシェイプ群を直接いじれることがわかったので未着手。

http://officetanaka.net/excel/vba/tips/tips174.htm