【Visio VBA極限攻略】図形が回っても文字は回さない!TextTransformセルをハックして「常に水平」を維持する超堅牢コード
プラント設計、電気系統図、オフィスレイアウト、あるいは大規模な業務フロー図。これらをVisioで自動生成・更新するシステムを構築した際、必ず現場から噴出するクレームがあります。
「図形を90度や180度回転させたら、中のテキストまで逆さまになって読めない。手動で直すのが苦痛すぎる」
この問題に対し、多くの開発者は「図形を回転させた後、VBAでテキストの角度(`TxtAngle`)をその都度計算して『値』を書き込む」という力技に逃げがちです。しかし、それは三流の設計です。図形が後からユーザーの手で再回転されたり、サイズ変更されたりした瞬間に、その絶対値は無意味化し、再びテキストは傾きます。
本級のプロフェッショナルが取るべきアプローチは、「図形がどう動こうが、文字は常に水平を維持する」という依存関係(数式)をShapeSheetに動的に埋め込むことです。
今回は、Visioの内部エンジン(ShapeSheet)の挙動を完全に掌握し、回転だけでなく「左右・上下反転(フリップ)」にも耐えうる、極めて堅牢な「テキスト水平保持」の自動化コードと、その設計思想を解説します。
—
1. なぜ「値の書き込み」は悪なのか? ShapeSheetをハックせよ
Visioのすべてのシェイプの裏には、ShapeSheetと呼ばれるスプレッドシートが存在します。図形の座標、色、そしてテキストの位置や角度もすべてこのシートのセルで管理されています。
テキストの配置情報を司るのが `Text Transform`(テキスト変換)セクション です。
| セル名 | 役割 |
| :— | :— |
| `TxtAngle` | テキストブロックの回転角度。デフォルトは `0 rad`(親シェイプの角度に対する相対角)。 |
| `TxtWidth` | テキストブロックの幅。 |
| `TxtHeight` | テキストブロックの高さ。 |
親シェイプが回転すると、シェイプ自体の `Angle` セルが変化します。デフォルト状態では、テキストは親シェイプに追従するため、画面上では傾いて見えます。
ここで、VBAから `shp.Cells(“TxtAngle”).ResultIU = 0` のように「0」という静的な値を書き込んではいけません。親シェイプの角度に追従して文字を常に水平(絶対角0度)に保つための正解は、「親の角度を打ち消す数式」をセルに仕込むことです。
数式設計の極意:回転を相殺する
理論上、テキストを水平に保つための数式は以下になります。
$$\text{TxtAngle} = -\text{Angle}$$
ShapeSheetの文字列表現としては、シンプルに `”-Angle”` となります。親図形が `30 deg` 回転すれば、テキストは自動的に `-30 deg` 回転し、結果として画面上は `0 deg`(水平)を維持します。
さらに深い罠:鏡像反転(Flip)への耐性
しかし、実務はこれだけでは終わりません。ユーザーが図形を「左右反転(FlipX)」または「上下反転(FlipY)」させた場合、文字が鏡に映したように反転してしまい、読めなくなります。
これを防ぐためには、`TxtWidth` と `TxtHeight` にも、親の反転状態を打ち消す数式を仕込む必要があります。
- `TxtWidth` の極限数式: `WidthIF(FlipX,-1,1)`
- `TxtHeight` の極限数式: `HeightIF(FlipY,-1,1)`
ここまで設計して初めて、「どんな操作をしても絶対に文字が読める、真に堅牢な図形」が完成します。
—
2. 堅牢なプロダクションコード(VBA)
以下に、実務でそのまま利用できる堅牢なVBAコードを示します。
このコードは、単に数式を書き込むだけでなく、「Text Transformセクションが存在しない図形(グループ化されていない単純な図形など)に対して、安全にセクションを追加してから処理する」という実務上のエラー回避策が組み込まれています。
Option Explicit
”’
”’
Public Sub ApplyConstantHorizontalText()
Dim targetPage As Visio.Page
Set targetPage = ActivePage
If targetPage Is Nothing Then
MsgBox “アクティブなページが見つかりません。”, vbCritical, “エラー”
Exit Sub
End If
‘ パフォーマンス最適化と画面描画の抑制
On Error GoTo ErrorHandler
ToggleVisioPerformance True
Dim targetSelection As Visio.Selection
Set targetSelection = ActiveWindow.Selection
Dim shp As Visio.Shape
‘ 選択されている図形があればそれに対して、なければページ全体の図形に対して処理
If targetSelection.Count > 0 Then
For Each shp In targetSelection
Call ConfigureTextTransformToHorizontal(shp)
Next shp
Else
For Each shp In targetPage.Shapes
Call ConfigureTextTransformToHorizontal(shp)
Next shp
End If
MsgBox “テキストの水平保持設定が完了しました。”, vbInformation, “処理完了”
CleanUp:
ToggleVisioPerformance False
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
”’
”’
”’ 対象のVisio.Shapeオブジェクト
Private Sub ConfigureTextTransformToHorizontal(ByVal shp As Visio.Shape)
If shp Is Nothing Then Exit Sub
‘ 1. TextTransformセクションが存在するか確認。なければ追加。
‘ ※Visioの仕様上、初期状態ではこのセクションを持たないシェイプが存在するため必須の処理。
If Not shp.SectionExists(visSectionTextXForm, True) Then
shp.AddSection visSectionTextXForm
End If
‘ 2. FormulaForceU を使用し、ローカル言語設定(日・英など)に依存しないユニバーサル数式を注入。
‘ TxtAngle: 親の回転角度(Angle)をマイナスして相殺する
shp.CellsU(“TxtAngle”).FormulaForceU = “-Angle”
‘ TxtPinX / TxtPinY: テキストの回転中心をシェイプの中央に固定
shp.CellsU(“TxtPinX”).FormulaForceU = “Width0.5”
shp.CellsU(“TxtPinY”).FormulaForceU = “Height0.5”
shp.CellsU(“TxtLocPinX”).FormulaForceU = “TxtWidth0.5”
shp.CellsU(“TxtLocPinY”).FormulaForceU = “TxtHeight0.5”
‘ 3. フリップ(反転)対策:親が反転していても文字を鏡像にしないための数式
shp.CellsU(“TxtWidth”).FormulaForceU = “WidthIF(FlipX,-1,1)”
shp.CellsU(“TxtHeight”).FormulaForceU = “HeightIF(FlipY,-1,1)”
‘ 再帰的に子シェイプ(グループ化されている場合)も処理
If shp.Type = visTypeGroup Then
Dim childShp As Visio.Shape
For Each childShp In shp.Shapes
Call ConfigureTextTransformToHorizontal(childShp)
Next childShp
End If
End Sub
”’
”’
Private Sub ToggleVisioPerformance(ByVal enableOptimization As Boolean)
With Application
If enableOptimization Then
.ScreenUpdating = 0
.DeferRecalc = 1
.EventsEnabled = 0
Else
.ScreenUpdating = 1
.DeferRecalc = 0
.EventsEnabled = 1
End If
End With
End Sub
—
3. チーフアーキテクトが解説する「なぜこのコードなのか」
このコードには、素人プログラマーが陥る罠を回避するための設計思想が散りばめられています。
① `Cells` ではなく `CellsU`、`Formula` ではなく `FormulaForceU` を使う理由
日本語環境のVisioで `Cells(“角度”)` や `Formula = “IF(真, 1, 0)”` と書かれたコードは、英語OSや多言語版Officeが導入されたグローバル環境で確実にクラッシュします。
`CellsU` および `FormulaForceU` を使用することで、Officeの言語設定に依存しないユニバーサル(U)なコードとなり、グローバル展開に耐えうる堅牢性が担保されます。
② `SectionExists` による事前の動的セクション追加
TextTransformセクション(`visSectionTextXForm`)は、すべての標準シェイプにデフォルトで存在するわけではありません。存在しないセルに対してVBAからアクセス(`shp.CellsU(“TxtAngle”)`)しようとすると、「予期しないエラー(セルのインデックスが無効です)」で即座に異常終了します。
処理の直前に `SectionExists` で存在を確認し、なければ `AddSection` で動的に作成するアプローチこそが、バグを未然に防ぐプロの防壁です。
③ パフォーマンスチューニング(`ToggleVisioPerformance`)
Visioは非常に強力なグラフィックエンジンを持っていますが、それゆえにVBAからShapeSheetを1セル書き換えるたびに、再描画と再計算(依存関係の解決)が走り、描画負荷が爆発します。
`Application.DeferRecalc = 1`(再計算の遅延)と `ScreenUpdating = 0` を並行して呼び出すことで、数百個のシェイプに対する一括処理スピードが数十倍に向上します。
—
4. データベース・外部システム連携時の注意点
CADデータや資産管理データベース(SQL Server等)から情報をインポートして自動的にVisioの系統図や配置図を生成する「データドライブ型」のシステムを構築する場合、以下の点に留意してください。
1. マスタシェイプ(ステンシル)側への組み込み検討
動的に生成するシェイプが特定されている場合、VBAで毎回数式を注入するのではなく、マスタシェイプ側のShapeSheetにあらかじめこの数式を埋め込んでおくのが最もスマートです。VBA側の役割は「マスタをドロップするだけ」に限定され、生成パフォーマンスがさらに向上します。
2. グループ化されたシェイプの扱い
グループ図形の場合、親グループに上記の数式を設定しても、グループ内の子図形(個々のパーツやテキストボックス)自体が個別に回転する設定になっていると、表示が崩れます。上記のサンプルコードでは `shp.Type = visTypeGroup` の判定を入れて再帰的に子シェイプを処理していますが、不要な子図形まで文字回転を抑止してしまわないよう、本番環境では対象シェイプの命名規則(例:名前に “Text” を含むものだけなど)でフィルタリングする処理を追加するとより洗練されます。
—
まとめ:真の自動化は「数式」を味方にすること
Visio VBAにおける最上流の設計思想は、「VBAで直接座標を計算して配置するのをやめ、ShapeSheetに計算式(関係性)を定義させる」ことに尽きます。
今回紹介したTextTransformセルのハックは、その最たる例です。一度数式を流し込んでしまえば、あとはVisioの標準エンジンがユーザーのドラッグや回転、反転といった操作を自動的にインターセプトし、文字の水平を維持し続けてくれます。
システムの保守性を高め、ユーザー体験を劇的に向上させるこのテクニックを、ぜひあなたのプロジェクトに導入してください。
