FAQ
アドイン コーディング パターン

ナレッジ番号:2613 | 登録日:2023/05/29 | 更新日:2023/12/28

サマリ

このトピックではアドインを構築する時に考慮すべき、いくつかのコーディング パターンについて述べています。

内容


アドイン内のコンポーネント間通信と調整

ほとんどのアドイン プロジェクトにおいて、あるコンポーネントをアクティブにしたり、他のコンポーネントの状態を取得するために、内部コンポーネント間の通信手段が必要となります。 たとえば、特定のアドイン ボタン コンポーネントをクリックすると、関連するアドインのドッカブル ウィンドウ コンポーネントに表示されているコンテンツが変わるといったような方法では、アドインの状態が全体的に変更されるかもしれません。

従来、コンポーネント オブジェクト モデル(COM)に基づいた仕組みは内部コンポーネント間通信を使用しており、カスタム インタフェースを定義して、それらのインターフェースは ボタン、ドッカブル ウィンドウ、あるいはエクステンション コンポーネントから公開されていました。これらのインタフェースには、フレームワークが提供する ICommandBar.Find や IApplication.FindExtension などの検索機能を使用してコンポーネントを見つけ出すことでアクセスできます。返された参照はカスタム インタフェースにキャストされて使用されます。このアプローチには以下の欠点があります。

  • COM インタフェースを使用すると、通信がシンプルな型と COM によってサポートされた呼び出しパターンに制限され、言語固有の“型とパターン”の使用が排除されてしまいます。
  • 同じプロジェクト内のコンポーネント間通信は、一般的に private です。COM インタフェースに登録すると、これらの private 通信の情報が表面上、公のものになり、プロジェクトの外部に公開することを目的とした public インタフェースを不必要に複雑化します。
  • COM インタフェースは少ないとはいえないコーディング ステップをさらに必要とし、管理者権限でのインストーラによるシステム レジストリへの登録を要求します。.NET では、そのようなインタフェースはプライマリ相互運用機能アセンブリ(PIAs) の生成とレジストレーションもまた必要であり、PIAs は(通信の両方の終端がマネージドである場合でも)各呼び出しのために、ネイティブとマネージドのコンテキストの複合的な切り替えを導入しています。

アドインは、その設計により COM パターンに依存することはなく、レジストリへの登録は不要であり、COM を利用した従来のアプローチはとりません。その代わりとなるパターンは単純であり、前述のような不便さはありません。すべてのフレームワークの型はシングルトンなので、内部コンポーネント間通信を実現するために、静的クラス メンバに基づいた単純なアプロ-チを行うことができます。

以下のコード例は、同じプロジェクト内で、他のコンポーネントに対しエクステンションがどのようにその機能を直接的に公開するのかを示しています。

public class MainExt: ESRI.ArcGIS.Desktop.AddIns.Extension
{
    private static MainExt s_extension;

    public MainExt()
    {
        s_extension = this;
    }

    internal static MainExt GetExtension()
    {
        //エクステンションは実行時に読み込まれ, 読み込みのために FindExtension が呼ばれる
        if (s_extension == null)
        {
            UID extID = new UIDClass();
            extID.Value = ThisAddIn.IDs.MainExt;
            ArcMap.Application.FindExtensionByCLSID(extID);
        }
        return s_extension;
    }

    internal void DoWork()
    {
        System.Windows.Forms.MessageBox.Show("Do work");
    }
}
 
Public Class MainExt
    Inherits ESRI.ArcGIS.Desktop.AddIns.Extension
    
    Private Shared s_extension As MainExt
    
    Public Sub New()
        s_extension = Me
    End Sub
    
    Friend Shared Function GetExtension() As MainExt
    'エクステンションは実行時に読み込まれ, 読み込みのために FindExtension が呼ばれる
    If s_extension Is Nothing Then
        Dim extID As UID = New UIDClass()
        extID.Value = ThisAddIn.IDs.MainExt;
        ArcMap.Application.FindExtensionByCLSID(extID)
    End If
    Return s_extension
End Function

Friend Sub DoWork()
    System.Windows.Forms.MessageBox.Show("Do work")
End Sub

End Class


以下のコード例は、同じアドイン プロジェクト内で一般的なクライアントがどのようにしてエクステンションにアクセスするのかを示しています。

protected override void OnClick()
{
    MainExt mainExt = MainExt.GetExtension();
    mainExt.DoWork();
}
Protected Overloads Overrides Sub OnClick()
Dim mainExt__1 As MainExt = MainExt.GetExtension()
mainExt__1.DoWork()
End Sub


AddIn.FromID 関数

アドインのフレームワークは、関連するアドイン ID 文字列が与えられたアドイン オブジェクト インスタンスへの参照を返す、静的 AddIn.FromID 関数も提供しています。この FromID 関数は、必要に応じて要求されたエクステンション、ドッカブル ウィンドウまたはコマンドが、適切に読み込まれ初期化されてから参照を返すことを確実にします。さらに ThisAddIn.IDs クラスを使用すると、拡張マークアップ言語 (XML)を経由せずにアドイン ID 文字列を取得することができます。ID クラスを使用すると誤字を避けることができ、拡張マークアップ言語(XML)構成ファイル内の ID が変更されても、コード内ですべての ID 文字列への参照を更新する必要がなくなります。

以下のコード例における OnClick の実装は、プロジェクト内のアドイン エクステンションである Addin1Ext インスタンスへの参照を得るためにパラメータ化された FromID 関数を利用しており、そしてカスタム メソッドを呼び出しています。ThisAddIn.IDs クラスはエクステンション ID を得るために使用されています。

protected override void OnClick()
{
    var ext = AddIn.FromID < Addin1Ext > (ThisAddIn.IDs.Addin1Ext);
    ext.MyCustomMethod();
}
Protected Overloads Overrides Sub OnClick()
Dim ext = AddIn.FromID(Of Addin1Ext)(ThisAddIn.IDs.Addin1Ext)
ext.MyCustomMethod()
End Sub

エクステンションの使用

よく設計されたプロジェクトでは、中心となるオブジェクトを使用して状態を保存し、プロジェクト内のコンポーネント間の振る舞いを連携させています。エクステンション オブジェクトは特にこの役割のために設計されていて、さまざまなアプリケーション イベントを受信し、アドインの状態を維持します。さらにアドインはオプションでドキュメント ストリーム内の永続的状態を保持し、復帰させることができます。以下の図を参照してください。

Image


コマンドの有効化と検査

アドイン コマンド―ボタンとツール―は OnUpdate メソッド経由で、有効/無効の状態が更新され、状態が検査されるよう定期的に通知されます。多くのコマンドがあることや OnUpdate メソッドは比較的頻繁に呼ばれるという理由から、アプリケーションの反応性に影響を与えることを避けるために、メソッド内のコードは最小限の量にします。多くの環境で、検査がアドイン エクステンション コンポーネントに送られ、そこでは関連する状態情報がさまざまなアプリケ-ション イベントに反応して、自動的に更新されてキャッシュされます。特にデータベース クエリのような時間を消費する操作は OnUpdate メソッド内で決して実行されるべきではありません。

以下のコードは簡単な例を提示しています。このケースでは、選択数が 0 より大きい時には常にコマンドが有効です。このコマンドは SelectionChanged イベントを受信して、Map オブジェクトへの呼び出しを最小限にしています。

protected override void OnUpdate()
{
    this.Enabled = m_enabled;
}

void mapEvents_SelectionChanged()
{
    m_enabled = m_focusMap.SelectionCount > 0;
}
Protected Overloads Overrides Sub OnUpdate()
Me.Enabled = m_enabled
End Sub

Private Sub mapEvents_SelectionChanged()
    m_enabled = m_focusMap.SelectionCount > 0
End Sub

以下のコード例はより現実的な状況を示していて、ここでは不十分であった実装が改善されています。

このボタンの初期の実装では OnUpdate 内で選択可能なフィーチャ レイヤに対して検査を行っています。この機能は特に、その他の多くのボタンに対してカット アンド ペーストをするとアプリケーションの反応性を低下させてしまいます。

protected override void OnUpdate()
{
    IMa p map = ArcMap.Document.FocusMap;
    // マップにレイヤがない場合は抜ける
    if (map.LayerCount == 0)
    {
        this.Enabled = false;
        return ;
    }
    // フォーカスマップのすべてのレイヤを取り出す
    // 少なくとも1つのレイヤが選択可能であるかどうか決定する
    UIDClass uid = new UIDClass();
    uid.Value = "{40A9E885-5533-11d0-98BE-00805F7CED21}";
    IEnumLayer enumLayers = map.get_Layers(uid, true);
    IFeatureLayer featureLayer = enumLayers.Next()as IFeatureLayer;
    while (featureLayer != null)
    {
        if (featureLayer.Selectable == true)
        {
            this.Enabled = true;
            return ;
        }
        featureLayer = enumLayers.Next()as IFeatureLayer;
    }
    this.Enabled = false;
}
Protected Overloads Overrides Sub OnUpdate()
Dim map As IMap = ArcMap.Document.FocusMap
' マップにレイヤがない場合は抜ける
If map.LayerCount = 0 Then
    Me.Enabled = False
    Exit Sub
End If
' フォーカスマップのすべてのレイヤを取り出す
' 少なくとも1つのレイヤが選択可能であるかどうか決定する
Dim uid As New UIDClass()
uid.Value = "{40A9E885-5533-11d0-98BE-00805F7CED21}"
Dim enumLayers As IEnumLayer = map.get_Layers(uid, True)
Dim featureLayer As IFeatureLayer = TryCast(enumLayers.[Next](), IFeatureLayer)
While featureLayer IsNot Nothing
    If featureLayer.Selectable = True Then
        Me.Enabled = True
        Exit Sub
    End If
    featureLayer = TryCast(enumLayers.[Next](), IFeatureLayer)
End While
Me.Enabled = False
End Sub
// ボタンの OnUpdate
protected override void OnUpdate()
{
    this.Enabled = m_mainExtension.HasSelectableLayer();
}

// エクステンションがマップのイベントを受信する
void MapEvents_ContentsChanged()
{
    m_hasSelectableLayer = CheckForSelectableLayer();
}

private bool CheckForSelectableLayer()
{
    IMap map = ArcMap.Document.FocusMap;
    //マップにレイヤがない場合は抜ける 
    if (map.LayerCount == 0)
        return false;
    // フォーカスマップのすべてのレイヤーを取り出す
    // 少なくとも1つのレイヤーが選択可能であるかどうか決定する
    UIDClass uid = new UIDClass();
    uid.Value = "{40A9E885-5533-11d0-98BE-00805F7CED21}";
    IEnumLayer enumLayers = map.get_Layers(uid, true);
    IFeatureLayer featureLayer = enumLayers.Next()as IFeatureLayer;
    while (featureLayer != null)
    {
        if (featureLayer.Selectable == true)
            return true;
        featureLayer = enumLayers.Next()as IFeatureLayer;
    }
    return false;
}

internal bool HasSelectableLayer()
{
    return m_hasSelectableLayer;
}
' ボタンの OnUpdate
Protected Overloads Overrides Sub OnUpdate()
Me.Enabled = m_mainExtension.HasSelectableLayer()
End Sub

' エクステンションがマップのイベントを受信する

Private Sub MapEvents_ContentsChanged()
    m_hasSelectableLayer = CheckForSelectableLayer()
End Sub

Private Function CheckForSelectableLayer() As Boolean
    Dim map As IMap = ArcMap.Document.FocusMap
    ' マップにレイヤがない場合は抜ける
    If map.LayerCount = 0 Then Return False
End If
' フォーカスマップのすべてのレイヤーを取り出す
' 少なくとも1つのレイヤーが選択可能であるかどうか決定する
Dim uid As New UIDClass()
uid.Value = "{40A9E885-5533-11d0-98BE-00805F7CED21}"
Dim enumLayers As IEnumLayer = map.get_Layers(uid, True)
Dim featureLayer As IFeatureLayer = TryCast(enumLayers.[Next](), IFeatureLayer)
While featureLayer IsNot Nothing
    If featureLayer.Selectable = True Then
        Return True
    End If
    featureLayer = TryCast(enumLayers.[Next](), IFeatureLayer)
End While
Return False
End Function

Friend Function HasSelectableLayer() As Boolean
    Return m_hasSelectableLayer
End Function

アプリケーション エクステンションを使用したドキュメント内のデータの永続性

アプリケーション エクステンションはマップ ドキュメントから直接データを読み取ったり、データを書き込んだりします。この永続性オプションはドキュンメント単位で状態を保存したり、再構築したりする際に便利です。たとえばカスタムな選択ツールにおいて、その関連するエクステンションが ”ユーザーが定義した選択時の環境設定” を永続化させることが可能になります。

ドキュメントが保存される時、読み込まれた各アプリケーション エクステンション コンポーネントは現在のドキュメント内で専用のストリームにデータを書き込む機会が与えられます。同様にドキュメントが読み込まれる時、各エクステンションはこのストリームからデータを読み込む機会が与えられます。このストリームにはどんなデータも書くことができます。

以下のコード例は2つの整数を書き込むエクステンションを示しています。

protected override void OnLoad(Stream inStrm)
{
    _selectionTolerance = Convert.ToInt32(inStrm.ReadByte());
    _ selectionThreshold = Convert.ToInt32(inStrm.ReadByte());
}

protected override void OnSave(Stream outStrm)
{
    outStrm.WriteByte(Convert.ToByte(_selectionTolerance));
    outStrm.WriteByte(Convert.ToByte(_selectionThreshold));
}
Protected Overloads Overrides Sub OnLoad(ByVal inStrm As Stream)
_selectionTolerance = Convert.ToInt32(inStrm.ReadByte())
Dim selectionThreshold As _ = Convert.ToInt32(inStrm.ReadByte())
                              EndSubProtectedOverloadsOverridesSub OnSave(ByVal outStrm As Stream)
outStrm.WriteByte(Convert.ToByte(_selectionTolerance))
outStrm.WriteByte(Convert.ToByte(_selectionThreshold))
End Sub

以下のコード例は文字列を書き込む 1 つの方法を示しています。

protected override void OnLoad(Stream inStrm)
{
    var binaryFormatter = new
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    _lastEditedBy = binaryFormatter.Deserialize(inStrm)asstring;
}

protected override void OnSave(Stream outStrm)
{
    var binaryFormatter = new
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    binaryFormatter.Serialize(outStrm, _lastEditedBy);
}
Protected Overloads Overrides Sub OnLoad(ByVal inStrm As Stream)
Dim binaryFormatter = New 

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
_lastEditedBy = TryCast(binaryFormatter.Deserialize(inStrm), String)
EndSubProtectedOverloadsOverridesSub OnSave(ByVal outStrm As Stream)
Dim binaryFormatter = New 

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
binaryFormatter.Serialize(outStrm, _lastEditedBy)
End Sub

カスタムなタイプとオブジェクト グラフを保存するために、ArcObjects の PersistHelper クラスを使用します。 以下のコード例は ArcObjects の型を扱う private 構造体を記述するエクステンションを示しています。

private MyPersistentData _data;

protected override void OnLoad(Stream inStrm)
{
    // 構造体を初期化する
    _data.Location = "";
    _data.Point = new ESRI.ArcGIS.Geometry.PointClass();

    PersistHelper.Load < MyPersistentData > (inStrm, ref _data);
}

protected override void OnSave(Stream outStrm)
{
    PersistHelper.Save < MyPersistentData > (outStrm, _data);
}

[Serializable()]
private struct MyPersistentData
{
    public string Location;
    public ESRI.ArcGIS.Geometry.PointClass Point;
}
Private _data As MyPersistentData

Protected Overloads Overrides Sub OnLoad(ByVal inStrm As Stream)
' 構造体を初期化する
_data.Location = ""
_data.Point = New ESRI.ArcGIS.Geometry.PointClass()
PersistHelper.Load(Of MyPersistentData)(inStrm, _data)
EndSubProtectedOverloadsOverridesSub OnSave(ByVal outStrm As Stream)
PersistHelper.Save(Of MyPersistentData)(outStrm, _data)
End Sub

 <Serializable()> 
Private Structure MyPersistentData
    Public Location AsStringPublic Point As ESRI.ArcGIS.Geometry.PointClass
End Structure

この文書は、ArcObjects Help for .NET Developers 内のトピック  Add-in coding patterns を元に翻訳したものです。

メタデータ

種類

製品

バージョン