Pull to refresh

Руководство по созданию Excel add-in для начинающих

Reading time 5 min
Views 48K
Здравствуй, %username%!

Недавно мне пришлось по долгу службы писать небольшой Excel add-in. К слову, до этого мой опыт работы с MS Office из-под .NET сводился к простеньким утилитам, генерировавшим отчеты в Excel при помощи Office Primary Interop Assemblies. А уж когда контора купила лицензии на Aspose.NET, взаимодействие с офисом через COM было забыто аки страшный сон. Так вот, начав разбираться в теме, я был неприятно удивлен отсутствием вменяемых русскоязычных туториалов для начинающих. Теперь хочу заполнить этот пробел. В качестве примера мы создадим add-in, который по нажатию кнопки генерирует на текущем листе список дней текущего месяца и выделяет выходные дни. Заинтересовавшихся прошу под кат!

Вместо предисловия


Заранее хочу оговориться, что сам я не профессионал в области разработки под MS Office, и здесь возможно найдутся люди, которые раскритикуют мое решение в пух и прах. Что ж, я буду рад любой конструктивной критике. Так же эта статья не является исчерпывающим руководством по написанию Excel Add-ins, не ставит цели подробно описать архитектуру Excel Add-ins или содержимое пространства имен Microsoft.Office.Core. Это вводная статья, которая, как я надеюсь, поможет новичку сориентироваться в новой для него теме и проведет через процесс создания add-in’а, который делает что-то осмысленное. Итак, приступим!

Готовим солюшен


Откроем студию и создадим новый проект типа Excel 2010 Add-in. Назовем его SampleAddIn:



Если у вас все получилось правильно (а что собственно здесь могло получиться неправильно?..), вы увидите солюшен со следующей структурой:



Единственный файл, который сгененрировала для нас студия – это ThisAddIn.cs:

public partial class ThisAddIn
{
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }
        
        #endregion
}


Здесь все просто – мы всего лишь регистрируем обработчики событий запуска и завершения нашего add-in’а. Сейчас, естественно, наш Add-in ничего не делает, но вы уже можете его запустить как обычный проект, по нажатию F5. Что особенно приятно, на этом этапе можно не заморачиваться развертыванием, т.к. студия сама запускает Excel с нужными настройками безопасности и подсовывает ему наш Add-in. Если все получилось, то при запуске Excel вы увидите такую надпись:



Рисуем интерфейс


Добавим в солюшен элемент типа Ribbon и назовем его SampleRibbon



В дизайнере добавим туда кнопку:



И зададим названия для вкладки, группы и кнопки:



А так же создадим обработчик нажатия кнопки:

public partial class SampleRibbon
{
        private void SampleRibbon_Load(object sender, RibbonUIEventArgs e)
        {
            
        }

        private void button1_Click(object sender, RibbonControlEventArgs e)
        {
            MessageBox.Show("Hello, world!");
        }
}


Попробуем запустить наш проект. Если вы все сделали правильно, то в Excel’е появилась новая вкладка с кнопкой, по нажатию на которую появляется окно с сообщением.

Ковыряем чуть глубже


На данный момент мы столкнулись с двумя проблемами: во-первых, если в классе ThisAddIn есть поле Application, дающее доступ к запущенному экземпляру Excel, то в классе Ribbon ничего подобного нет, а следовательно непонятно, как именно взаимодействовать с листами Excel’я. Во-вторых, как мы все знаем, смешение логики отображения и бизнес-логики является отличной заготовкой для хорошей порции спагетти в вашем коде. Поэтому изменим код, так чтобы избавиться от этих двух проблем, а заодно разберемся в «магии», которая встроила наш Ribbon в Excel без каких-либо действий с нашей стороны.

За создание пользовательских лент в Add-in’е отвечает метод CreateRibbonExtensibilityObject из класса AddInBase, от которого на самом деле унаследован наш класс ThisAddIn. Слава богу, этот метод был предусмотрительно сделан виртуальным, так что перегрузить его не составит проблем. Для начала добавим в класс ThisAddIn код:

protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
        return null;
}


И запустим проект. Вуаля! Вкладка исчезла. Теперь изменим класс SampleRibbon:

public partial class SampleRibbon
{
        public event Action ButtonClicked;

        private void SampleRibbon_Load(object sender, RibbonUIEventArgs e)
        {
            
        }

        private void button1_Click(object sender, RibbonControlEventArgs e)
        {
            if (ButtonClicked != null)
                ButtonClicked();
        }
}


И класс ThisAddIn:

protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
        var ribbon = new SampleRibbon();
        ribbon.ButtonClicked += ribbon_ButtonClicked;
        return Globals.Factory.GetRibbonFactory().CreateRibbonManager(new IRibbonExtension[] { ribbon });
}

private void ribbon_ButtonClicked()
{
         MessageBox.Show("Hello, world!");
}


Запустим проект. Если вы все сделали правильно, то вкладка снова появилась, а по нажатию кнопки, как и раньше выводится окно с сообщением.

Работаем с ячейками


Теперь пришло время для того, ради чего собственно и был затеян весь это сыр-бор – генерации таблицы. Для начала создадим пару вспомогательных методов для оформления ячеек:

public static void MarkBold(dynamic border)
{
        border.Weight = XlBorderWeight.xlMedium;
}


Этот метод выделяет границу ячейки жирным. Кстати, обратите внимание на тип аргумента border – dynamic – начиная с .NET 4.0 это очень удобный способ работы с COM-объектами! Если вдруг вы не в курсе, рекомендую ознакомиться, например, с этой статьей. Для нашего удобства создадим еще пару методов:

public enum Border
{
        Left = 1,
        Right = 2,
        Top = 3,
        Bottom = 4
 }

private void MarkCell(dynamic cell, DateTime day)
{
        if (day.DayOfWeek == DayOfWeek.Saturday)
        {
               MarkBold(cell.Borders[Border.Left]);
               MarkBold(cell.Borders[Border.Top]);
               MarkBold(cell.Borders[Border.Bottom]);
        }
        if (day.DayOfWeek == DayOfWeek.Sunday)
        {
               MarkBold(cell.Borders[Border.Right]);
               MarkBold(cell.Borders[Border.Top]);
               MarkBold(cell.Borders[Border.Bottom]);
        }
        cell.Columns[1].ColumnWidth = 4;
}


Теперь напишем код для получения списка дней месяца:

private static IEnumerable<DateTime> GetDaysOfCurrentMonth()
{
        var today = DateTime.Today;
        var dayIter = new DateTime(today.Year, today.Month, 1);
        while (dayIter.Month == today.Month)
        {
               yield return dayIter;
               dayIter = dayIter.AddDays(1);
         }
}


И сведем это все воедино:

private void ribbon_ButtonClicked()
{
        GenerateTable(Application.ActiveWorkbook.ActiveSheet, 1, 1);
}

private void GenerateTable(dynamic wSheet, int row, int column)
{
         var currentMonth = GetDaysOfCurrentMonth().ToArray();
         for (int i = 0; i < currentMonth.Length; i++)
         {
                wSheet.Cells[row, column + i] = currentMonth[i].Day;
                MarkCell(wSheet.Cells[row, column + i], currentMonth[i]);
         }
}


Если вы нигде не ошиблись, то запустив проект, и нажав на кнопку на нашей вкладке, вы увидите нечто подобное:



Вместо заключения


Вот и все, я надеюсь, после прочтения этой статьи вы получили базовые представления о создании Excel Add-ins под .NET. Для дальнейшего чтения могу порекомендовать, конечно же, MSDN и этот блог – он мне очень помог при изучении этой темы. Всем удачи и спасибо за внимание!
Tags:
Hubs:
+27
Comments 11
Comments Comments 11

Articles