VBAからGDI+を使う資料集
inet上の達人の方々から御教示いただいたコードを少しアレンジさせてもらっています(切り貼りとも言います)。
  1. ホーム
  2. Other
  3. validation


動的な入力規則生成をMSXML DOMでやってみた

入力規則を動的に生成して、3段階の絞り込みを行うという課題があるが、ふと思い立ってMSXML DOMでやってみました。
久しぶりに復習してみただけで(すっかり忘れておりました)仕事につかったら傍迷惑きわまりないですね。
NodeとElementの違いがいまいち分かっておりません。前者だとgetElementsByTagNameが使えません。
Nodeのつもりで取得して、TypeNameを表示してみるとElementであってみたり...


☆入力規則生成用のデータ(Sheets(2))
大分類	中分類	小分類
A	A1	A11
A	A1	A12
A	A2	A21
B	B1	B11
B	B1	B12
B	B2	B21
B	B2	B22
B	B3	B31
(中略)
C	C1	C11
C	C1	C13
(以下略)

☆標準モジュール
'Microsoft XML V3.0に参照設定

Public oXMLDom As DOMDocument30

'MSXMLDOMの設定
Private Sub settingDOM(ByRef dom As DOMDocument30)
  With dom
    .async = False
    .validateOnParse = False
    .resolveExternals = False
    .preserveWhiteSpace = True
    .setProperty "SelectionLanguage", "XPath"
  End With
End Sub

'XMLのヘッダー?の部分を設定
Private Sub setInfo(ByRef dom As DOMDocument30, comment As String)
  Dim node As IXMLDOMNode
  
  Set node = dom.createProcessingInstruction("xml", "version=""1.0"" encoding=""Shift_JIS""")
  dom.appendChild node
  Set node = Nothing
  Set node = dom.createComment(comment)
  dom.appendChild node
  Set node = Nothing
End Sub

Public Sub setDOM()
  'Dim oXMLDdom ...
  Dim majorCatRange As Range, middleCatRange As Range, minorCatRange As Range
  Dim myCell As Range
  Dim i As Long, j As Long, k As Long
  Dim myXPath As String
  
  Dim root As IXMLDOMElement
  Dim newElement As IXMLDOMElement
  Dim nexElementsText As IXMLDOMText
  Dim retNode As IXMLDOMNodeList
  Dim majorNodeElem As IXMLDOMElement, middleNodeElem As IXMLDOMElement, minorNodeElem As IXMLDOMElement
    
  Set oXMLDom = New DOMDocument30
  '初期設定
  settingDOM oXMLDom
  setInfo oXMLDom, "xml test"
  'ワークシートから絞り込み選択用リスト取得
  With ThisWorkbook.Sheets(2)
    Set majorCatRange = Range(.Range("A2"), .Range("A" & .Rows.Count).End(xlUp))
    Set middleCatRange = Range(.Range("B2"), .Range("B" & .Rows.Count).End(xlUp))
    Set minorCatRange = Range(.Range("C2"), .Range("C" & .Rows.Count).End(xlUp))
  End With
  
  Set root = oXMLDom.createElement("root")
  'これを入れ忘れて多方面ではまった
  oXMLDom.appendChild root
  '大分類
  For Each myCell In majorCatRange.Cells
    Set retNode = root.getElementsByTagName(myCell.Value)
    If retNode.Length = 0 Then
      Set newElement = oXMLDom.createElement(myCell.Value)
      root.appendChild newElement
    End If
  Next myCell
  '中分類
  For Each myCell In middleCatRange.Cells
    Set majorNodeElem = root.getElementsByTagName(myCell.Offset(0, -1).Value).Item(0)
    Set retNode = majorNodeElem.getElementsByTagName(myCell.Value)
    If retNode.Length = 0 Then
      Set newElement = oXMLDom.createElement(myCell.Value)
      majorNodeElem.appendChild newElement
    End If
  Next myCell
  
  '小分類
'  For Each myCell In minorCatRange.Cells
'    Set majorNodeElem = root.getElementsByTagName(myCell.Offset(0, -2).Value).Item(0)
'    Set middleNodeElem = majorNodeElem.getElementsByTagName(myCell.Offset(0, -1).Value).Item(0)
'    Set retNode = middleNodeElem.getElementsByTagName(myCell.Value)
'    If retNode.Length = 0 Then
'      Set newElement = oXMLDom.createElement(myCell.Value)
'      middleNodeElem.appendChild newElement
'    End If
'  Next myCell

  '小分類ををXPathでやってみた
  For Each myCell In minorCatRange.Cells
    myXPath = "/" & root.nodeName & "/" & myCell.Offset(0, -2).Value & "/" & myCell.Offset(0, -1).Value
    
    '下記どの書き方でもOK
    Set middleNodeElem = root.SelectSingleNode(myXPath)
'    Set middleNodeElem = oXMLDom.DocumentElement.SelectSingleNode(myXPath)
'    Set middleNodeElem = oXMLDom.SelectSingleNode(myXPath)
    Set retNode = root.SelectNodes(myXPath & "/" & myCell.Value)
    If retNode.Length = 0 Then
      Set newElement = oXMLDom.createElement(myCell.Value)
      middleNodeElem.appendChild newElement
    End If
  Next myCell
End Sub

☆生成したXMLの例
'<?xml version="1.0"?>
'<!--xml test-->
'<root><A><A1><A11/><A12/><A13/></A1><A2><A21/><A22/></A2><A3><A31/></A3></A><B><B1><B11/><B12/><B13/></B1><B2><B21/><B22/></B2><B3><B31/></B3></B><C><C1><C11/><C12/><C13/></C1><C2><C21/><C22/></C2><C3><C31/><C32/></C3></C></root>

☆ワークシートモジュール
A→B→C列にWorksheet_SelectionChangeのイベントにより動的に入力規則を生成します

Private Sub Worksheet_SelectionChange(ByVal target As Range)
  If target.Cells.Count > 1 Then Exit Sub
  If target.Column > 3 Then Exit Sub
  If oXMLDom Is Nothing Then setDOM
  setValidation target
End Sub

Private Sub setValidation(target As Range)
  Dim i As Long
  Dim myXPath As String
  Dim root As IXMLDOMElement
  Dim retNode As IXMLDOMNodeList
  Dim strValidation As String

  Set root = oXMLDom.DocumentElement
  
  Select Case target.Column
    Case 1
      Set retNode = root.ChildNodes
    Case 2
      If target.Offset(0, -1).Value = "" Then Exit Sub
      myXPath = "/" & root.nodeName & "/" & target.Offset(0, -1).Value
      Set retNode = root.SelectSingleNode(myXPath).ChildNodes
    Case 3
      If target.Offset(0, -1).Value = "" Or target.Offset(0, -2).Value = "" Then Exit Sub
      myXPath = "/" & root.nodeName & "/" & target.Offset(0, -2).Value & "/" & target.Offset(0, -1).Value
      Set retNode = root.SelectSingleNode(myXPath).ChildNodes
    Case Else
  
  End Select
  If retNode Is Nothing Then Exit Sub
  For i = 0 To retNode.Length - 1
    If strValidation = "" Then
      strValidation = retNode(i).nodeName
    Else
      strValidation = strValidation & "," & retNode(i).nodeName
    End If
    setvalidationSub target, strValidation
  Next i
End Sub

Private Sub setvalidationSub(target As Range, strValidation As String)
    With target.Validation
        .Delete
        .Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
        xlBetween, Formula1:=strValidation
        .IgnoreBlank = True
        .InCellDropdown = True
        .InputTitle = ""
        .ErrorTitle = ""
        .InputMessage = ""
        .ErrorMessage = ""
        .IMEMode = xlIMEModeNoControl
        .ShowInput = True
        .ShowError = True
    End With
End Sub

おまけで、実行時バインディング版
================================================================
☆標準モジュール
Public oXMLDom As Object

Public Sub setDOM()
  Dim catRange As Range, myCell As Range
  Dim myXPath As String
  Dim root As Object, retNode As Object
    
  Set oXMLDom = CreateObject("MSXML2.DOMDocument")
  oXMLDom.setProperty "SelectionLanguage", "XPath"
  'ワークシートから絞り込み選択用リスト取得
  With ThisWorkbook.Sheets(2)
    Set catRange = Range(.Range("A2"), .Range("C" & .Rows.Count).End(xlUp))
  End With
  'DOMにリストを設定
  Set root = oXMLDom.createElement("root")
  oXMLDom.appendChild root
  '大分類
  For Each myCell In catRange.Columns(1).Cells
    Set retNode = root.getElementsByTagName(myCell.Value)
    If retNode.Length = 0 Then
      root.appendChild oXMLDom.createElement(myCell.Value)
    End If
  Next myCell
  '中分類
  For Each myCell In catRange.Columns(2).Cells
    myXPath = "/" & root.nodeName & "/" & myCell.Offset(0, -1).Value
    Set retNode = root.SelectNodes(myXPath & "/" & myCell.Value)
    If retNode.Length = 0 Then
      root.SelectSingleNode(myXPath).appendChild oXMLDom.createElement(myCell.Value)
    End If
  Next myCell
  '小分類
  For Each myCell In catRange.Columns(3).Cells
    myXPath = "/" & root.nodeName & "/" & myCell.Offset(0, -2).Value & "/" & myCell.Offset(0, -1).Value
    Set retNode = root.SelectNodes(myXPath & "/" & myCell.Value)
    If retNode.Length = 0 Then
      root.SelectSingleNode(myXPath).appendChild oXMLDom.createElement(myCell.Value)
    End If
  Next myCell
End Sub

☆シートモジュール
Private Sub Worksheet_SelectionChange(ByVal target As Range)
  If target.Cells.Count > 1 Then Exit Sub
  If target.Column > 3 Then Exit Sub
  If oXMLDom Is Nothing Then setDOM
  setValidation target
End Sub

Private Sub setValidation(target As Range)
  Dim i As Long
  Dim myXPath As String
  Dim root As Object, retNode As Object
  Dim strValidation As String

  Set root = oXMLDom.DocumentElement
  Select Case target.Column
    Case 1
      myXPath = "/" & root.nodeName
    Case 2
      If target.Offset(0, -1).Value = "" Then Exit Sub
      myXPath = "/" & root.nodeName & "/" & target.Offset(0, -1).Value
    Case 3
      If target.Offset(0, -1).Value = "" Or target.Offset(0, -2).Value = "" Then Exit Sub
      myXPath = "/" & root.nodeName & "/" & target.Offset(0, -2).Value & "/" & target.Offset(0, -1).Value
  End Select
  Set retNode = root.SelectSingleNode(myXPath).ChildNodes
  If retNode Is Nothing Then Exit Sub
  For i = 0 To retNode.Length - 1
    If strValidation = "" Then
      strValidation = retNode(i).nodeName
    Else
      strValidation = strValidation & "," & retNode(i).nodeName
    End If
    With target.Validation
        .Delete
        .Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
        xlBetween, Formula1:=strValidation
    End With
  Next i
End Sub