Excel-DNAで遊ぼう(1)

じゃあVisual StudioExcelのアドイン作ればいいんじゃね?と言うことで、Excel-DNAで遊びましょう。

どんなことが出来るとか、どうやって導入するとか、どんなメリットがあるかとか、そう言うのはもう先人が書いているので適当に読んでください。日本語リファレンスなんて甘えたものは存在しません。って言うか、ぶっちゃけた話、チュートリアル的なものすらほとんど見つかりません。(ないことはないけど
ちょっとしたWrapperを作ればセルに対してLINQをばんばん使っていけるし、Marshal.ReleaseComObject地獄なんてものは存在しないし、3〜4つのAttributeを覚えればアドイン登録からショートカットキーの設定までやってくれます。アドインだけでなく、ユーザー定義関数(UDF)も作れます。
.NETの資産は全部使えるのでWindows FormだろうがWPFだろうが何の問題もなく呼べます。あのクソエディタだけでなくあのクソフォームからも解放されるのです。

と言うわけで適当にコードを載せていきましょう。
1ブックの中に40枚程度のシートとか言うとんでもないテーブル一覧表の中からある1枚のシートを探している時に「なんでExcel 2010にもなって自分で入力した文字列からシートを探す機能がついてないんだ?Excel開発者はもしかしてアホなのか?」と憤り、「まぁアホなら仕方ない。自分で作ろう。」と思った結果出来たやつです。

using System;
using System.Windows.Forms;
using ExcelDna.Integration;

namespace ExcelPlus
{
    public partial class SeekDialog : Form
    {
        public SeekDialog()
        {
            InitializeComponent();
        }

        private void SeekDialog_Load(object sender, EventArgs e)
        {
            foreach (var worksheet in (ExcelDnaUtil.Application as dynamic).Worksheets)
            {
                LstSheet.Items.Add(worksheet.Name);
                CmbSheet.Items.Add(worksheet.Name);
            }
        }

        private void BtnShow_Click(object sender, EventArgs e)
        {
            if (LstSheet.SelectedIndex == -1 && string.IsNullOrEmpty(CmbSheet.Text)) return;

            var sheetName = !string.IsNullOrEmpty(CmbSheet.Text) ? CmbSheet.Text : LstSheet.Items[LstSheet.SelectedIndex] as string;

            if (!CmbSheet.Items.Contains(sheetName))
            {
                MessageBox.Show("シートが見つかりませんでした", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            (ExcelDnaUtil.Application as dynamic).Worksheets[sheetName].Activate();

            this.Close();
        }

        private void BtnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}

画像?そんなもんはないよ。

仕組みは非常に単純です。Load時にアクティブなブックからすべてのWorksheetを引っこ抜き、一覧を表示するListBoxと検索用のComboBoxに入れています。

private void SeekDialog_Load(object sender, EventArgs e)
{
    foreach (var worksheet in (ExcelDnaUtil.Application as dynamic).Worksheets)
    {
        LstSheet.Items.Add(worksheet.Name);
        CmbSheet.Items.Add(worksheet.Name);
    }
}

CmbSheetのAutoCompleteModeプロパティにSuggestAppendを指定しているので、シート名の頭の何文字かを入れれば勝手に候補が出てきます。

表示ボタンが押されたらComboBoxに入力されたシート or ListBoxから選択されたシートを表示します。

private void BtnShow_Click(object sender, EventArgs e)
{
    if (LstSheet.SelectedIndex == -1 && string.IsNullOrEmpty(CmbSheet.Text)) return;

    var sheetName = !string.IsNullOrEmpty(CmbSheet.Text) ? CmbSheet.Text : LstSheet.Items[LstSheet.SelectedIndex] as string;

    if (!CmbSheet.Items.Contains(sheetName))
    {
        MessageBox.Show("シートが見つかりませんでした", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    (ExcelDnaUtil.Application as dynamic).Worksheets[sheetName].Activate();

    this.Close();
}
(ExcelDnaUtil.Application as dynamic).Worksheets[sheetName].Activate();

ここがVBAだと

Application.Worksheets(sheetName).Activate

にあたる部分です。わかりやすいでしょ?

で、Formを作ったはいいものの、このままではExcelから呼べないので、適当にFormをnewしてShowするpublic staticなメソッドを作ってあげます。

private static SeekDialog _sd = null;

/// <summary>シート検索</summary>
[ExcelCommand(MenuName = "Sheet", MenuText = "検索", ShortCut = "^S")]
public static void CallSheetSheekerDialog()
{
    if (_sd == null || _sd.IsDisposed)
    {
        _sd = new SeekDialog();
        _sd.Show();
    }
    else
    {
        _sd.Activate();
    }
}

メソッドのAttributeにExcelCommandを指定してやり、MenuName、MenuTextあたりをとりあえずでも指定すれば動きます。
ShortCutで使用できる書式は何故かまとまった資料がないのでよくわからないんですが、多分Application.OnKeyとかApplication.MacroOptionsっぽい書式ならいけるんじゃないかなと。(適当)
ちなみにこの設定だとCtrl+Shift+Sキーで動きます。

微妙にひっかかりやすいポイントとしては、このAttributeが設定されているクラスのスコープを明示的に「public class」で指定しないと読み込まれません。後、あらゆるpublic staticなメソッドがUDFにされてしまうので不要なものは[ExcelFunction(IsHidden = true)]を指定してあげましょう。