среда, 9 февраля 2011 г.

Использование WORD шаблонов C# 4.0

Инструментарий:
• Microsoft Visual Studio 2010
• Microsoft Office 2010

исходники к статье


Данная статья является адаптацией более ранней статьи «Использование WORD шаблонов» с учетом новых возможностей вошедших в C# 4.0 таких как: именованные параметры, пропуск ключевого слова ref при работе с COM, индексируемые свойства и необязательные параметры. Вы можете сравнить количество кода с предыдущей статьей и увидеть, что кода сало много меньше, так же он стал более читабельным.

Все программы работают с данными и довольно часто их приходится выводить на печать, или отправлять по электронной почте, к тому же, бывает необходимо не просто послать сухие списки, а выделить какую-нибудь информацию, повышая ее важность при диагональном прочтении, или создать красивые графики с таблицами для любимого начальства и все это обязательно на фирменном бланке.

Для решения этой задачи могут пригодиться шаблоны WORD, ведь в большинстве своем для каждого случая существует свой, заранее разработанный бланк, в который просто подставляются текущие данные.

Предположим, существует некая компания «Ключи и Отвертки», которая занимается продажами обозначенных инструментов, и менеджеры компании хотят видеть ежедневный отчет о продажах в своих офисах.

Начнем с простого шаблона, который будет представлять из себя фирменный бланк компании и единственное поле для вставки данных. Создадим новый WORD документ и уменьшим отступы (Page Layout -> Margins -> Narrow).


Margins


После этого развернем документ в альбомную ориентацию (Page Layout -> Orientation -> Landscape).

Orientation

Далее создадим шапку (Insert -> Header –> Alpabet).

Footer

Напишем название. Далее впишем дополнительную информацию о компании и пометим ее курсивом с выравниванием по правому краю.

После создания шапки в документ автоматически был добавлен пустой подвал, перейти в редактирование которого можно просто перенеся туда курсор. Визуально отделим его от тела документа при помощи разделительной линии. (Insert –> Shapes -> Lines)

Line

Далее, в подвале сделаем выравнивание по правому краю и создадим закладку (Bookmark), которую будем отыскивать в коде и на ее место вставлять данные (Insert -> Bookmark).

InsertBookmark

И назовем ее AuthorName.

На этом завершим создание фирменного бланка, сохраним шаблон с именем screwdriver.dotx (Word Template)

Следующим шагом создадим из кода новый документ Word, на основе нашего шаблона, впишем автора документа и сохраним его на диск.

Создадим в Visual Studio WPF приложение с названием UsingWordTemplate ( File -> New -> Project -> Windows -> WPF Application).

Добавим на форму Label (Content="Автор:"), TextBox (Name=”autorTxtb”), в который будем вписывать имя автора документа и кнопку (Content="Сохранить" Name=”saveBtn”), с помощью которой будем сохранять созданный документ.

Добавим ссылку на .Net библиотеку Microsoft.Office.Interop.Word Version 14. В шапке кода главного окна добавим


using Word = Microsoft.Office.Interop.Word;


После чего определим переменную


Word._Application oWord = new Word.Application();


Переменная oWord будет представлять процесс WINWORD.EXE в памяти.

Так же определим событие формы Closing, в котором мы будем закрывать процесс WINWORD.EXE


private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
oWord.Quit();
}



Примечание:

В предыдущем фрагменте кода мы незаметно использовали новую возможность C# 4.0 - необязательные параметры. Если сравнить с предыдущим вариантом статьи, то тогда нам потребовалось определить переменную oMissing представляющую из себя аргумент представляющий значение по умолчанию, который требуют методы из пространства Microsoft.Office.Interop. И вызов выглядел следующим образом:


object oMissing = System.Reflection.Missing.Value;

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
}




В противном случае, после закрытия нашей программы, в процессах будет висеть не выгруженный WINWORD.EXE процесс.

Вызовем контекстное меню текущего проекта и добавим в него заранее созданный шаблон screwdriver.dotx (Add -> Existing Item -> screwdriver.dotx), в его свойствах определим Copy To Output Directory как Copy always.

Определим обработчик кнопки:


private void saveBtn_Click(object sender, RoutedEventArgs e)
{
_Document oDoc = GetDoc(Environment.CurrentDirectory + "\\screwdriver.dotx");
oDoc.SaveAs(FileName: Environment.CurrentDirectory + "\\New.docx");
oDoc.Close();
}


Загружаем сохраненный шаблон, заполняем его данными с формы и сохраняем на диск, после чего закрываем документ.


Примечание:

Здесь мы вновь используем пропуск ключевого слова ref,необязательные параметры и так же новую возможность – именованные параметры. Для сравнения посмотрим на код которым мы пользовались ранее:

private void SaveToDisk(Word._Document oDoc, string filePath)
{
object fileName = filePath;
oDoc.SaveAs(ref fileName, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
}





Примечание:

Остерегайтесь использования Environment.CurrentDirectory так как значение может измениться в процессе работы.


static void Main(string[] args)
{
Console.WriteLine(Environment.CurrentDirectory);
Environment.CurrentDirectory = "c:\\";
Console.WriteLine(Environment.CurrentDirectory);
}


Как показано выше мы можем сами изменить ее значение, в данном случае Environment.CurrentDirectory больше указывает не на каталог приложения, а на корень диска C.


Ниже приведен код вызванных методов:


private _Document GetDoc(string path)
{
_Document oDoc = oWord.Documents.Add(path);
SetTemplate(oDoc);
return oDoc;
}

private void SetTemplate(Word._Document oDoc)
{
oDoc.Bookmarks["AuthorName"].Range.Text = autorTxtb.Text;
}


Сигнатуры вызываемых методов из пространства имен Microsoft.Office.Interop можно посмотреть по адресу:

http://msdn.microsoft.com/ru-ru/library/microsoft.office.interop.word%28en-us%29.aspx

Введем в текст бокс данные автора документа “Александр Кобелев” и сохраним документ на диск нажав кнопку “Сохранить”. В папке Debug нашего приложения появится файл New.docx, содержащий введенные данные на месте bookmark AuthorName.

Теперь добавим еще одну кнопку (Content="Print" Name="prntBtn") на форму, которая будет распечатывать документ на принтер и определим ее обработчик.


private void prntBtn_Click(object sender, RoutedEventArgs e)
{
_Document oDoc = GetDoc(Environment.CurrentDirectory + "\\screwdriver.dotx");
PrintDoc(oDoc);
oDoc.Close(WdSaveOptions.wdDoNotSaveChanges)
}


Здесь все то же самое, что и в предыдущем случае, только вместо метода SaveToDisk вызывается PrintOut() и перед закрытием документа явно определяется, что документ не стоит сохранять. В предыдущем случае, при сохранении документа на диск, он помечался как сохраненный и не требовалось явно указывать этот параметр.

Теперь добавим на шаблон таблицу 3 на 4, которая будет отображать продажи по городам. Верхняя строчка обозначит ключи и отвертки, а первый столбик города: Москва, Санкт-Петербург, Челябинск. Разукрасим таблицу по своему вкусу.

table

Далее мы могли бы поставить шесть закладок (boomark), задать им уникальные имена и искать их как в предыдущем случае, но это слишком накладно. Можно поступить проще – выделить всю таблицу и добавить закладку на все выделение сразу.

selectTable

Назовем новую закладку “SaleTable”.

Добавим на форму DataGrid, со свойством AutoGenerateColumns="True", а в код новую переменную

В конструкторе вызовем метод, который заполнит DataTable и свяжет его с DataGrid


public MainWindow()
{
InitializeComponent();
InitializeDataGrid();
}

private void InitializeDataGrid()
{
table = new DataTable();
table.Columns.Add();
table.Columns.Add();
table.Columns.Add();
var row = table.NewRow();
row[0] = "Город";
row[1] = "Ключи";
row[2] = "Отвертки";
table.Rows.Add(row);
table.Rows.Add(table.NewRow()[0] = "Москва");
table.Rows.Add(table.NewRow()[0] = "Санкт-Петербург");
table.Rows.Add(table.NewRow()[0] = "Челябинск");
dataGrid1.DataContext = table;
}


Добавим в метод SetTemplate() вызов метода SetTable() который будет заполнять данными таблицу.


private void SetTemplate(Word._Document oDoc)
{
. . .
SetTable(oDoc,"SaleTable",table);
}

private void SetTable(Word._Document oDoc, string bookmark, DataTable dataContext)
{
dynamic tbl = oDoc.Bookmarks[bookmark].Range.Tables[1];
int tblRow = 0;
int tblCell = 0;
foreach (Word.Column col in tbl.Columns)
{
foreach (Word.Cell cell in col.Cells)
{
SetCell(cell, (string)dataContext.Rows[tblRow][tblCell]);
tblRow++;
}
tblCell++;
tblRow = 0;
}
}


Как видно из листинга, мы перебираем по очереди все ячейки в шаблоне и сопоставляем их с данными из DataTable.

Продолжая развивать идею предположим, что менеджерам компании было бы удобно, если бы при очень низких значениях продаж (меньше 100), в отчете это отмечалось красной ячейкой, сразу бросающейся в глаза и наоборот, если уровень продаж высок (больше 500), ячейка помечалась зеленым цветом.

Для реализации немного изменим метод SetTable() , заменив явное присваивание на вызов метода SetCell().


...
foreach (Word.Cell cell in col.Cells)
{
SetCell(cell, (string)dataContext.Rows[tblRow][tblCell]);
tblRow++;
}
...

private void SetCell(Word.Cell cell, string text)
{
int val = 0;
if (int.TryParse(text, out val))
{
if (val < 100) cell.Shading.BackgroundPatternColor = Word.WdColor.wdColorRose;
if (val > 500) cell.Shading.BackgroundPatternColor = Word.WdColor.wdColorLightGreen;
}
cell.Range.Text = text;
}


В методе SetCell() пытаемся применить парсинг к строковому аргументу и, при удачном исходе, в зависимости от значения, задаем фон для ячейки таблицы.

Перейдем к графикам.

Для использования графиков потребуется ссылка на .Net библиотеку Microsoft.Office.Interop.Excel Version 14. Так же в шапке кода главного окна добавим using


using Excel = Microsoft.Office.Interop.Excel;


Сделаем несколько отступов вниз, для создания пространства между таблицей и графиком, добавим в шаблон график (Insert -> Chart -> Column -> 3d Column)

insertChart

3dColumn

и выставим все значения по нулям.

chartDefValue

После чего выделим график на шаблоне (кликнув по краю графика) и создадим новую закладку “ChartBookmark”, далее сохраним изменения в шаблоне.

Добавим в метод SetTemplate() вызов метода SetChart(), который будет заполнять данными график.


private void SetTemplate(Word._Document oDoc)
{
...
SetChart(oDoc, "ChartBookmark", table);
}

private void SetChart(Word._Document oDoc, string bookmark, DataTable dataContext)
{
Word.Chart chart = oDoc.Bookmarks[bookmark].Range.InlineShapes[1].Chart;
Word.ChartData chartData = chart.ChartData;
chartData.Activate();

Excel.Workbook dataWorkbook = (Excel.Workbook)chartData.Workbook;
Excel.Worksheet dataSheet = (Excel.Worksheet)dataWorkbook.Worksheets[1];
dataSheet.Cells.Range["B2"].FormulaR1C1 = dataContext.Rows[1][1];
dataSheet.Cells.Range["B3"].FormulaR1C1 = dataContext.Rows[1][2];
dataSheet.Cells.Range["C2"].FormulaR1C1 = dataContext.Rows[2][1];
dataSheet.Cells.Range["C3"].FormulaR1C1 = dataContext.Rows[2][2];
dataSheet.Cells.Range["D2"].FormulaR1C1 = dataContext.Rows[3][1];
dataSheet.Cells.Range["D3"].FormulaR1C1 = dataContext.Rows[3][2];
dataWorkbook.Close();
}


Теперь приложение полностью готово к работе, введем некоторые данные, чтобы провести функциональный тест. В строке автор я введу “Александр Кобелев” а значения продаж по городам у меня будут такие: Ключи/Отвертки Москва – 700/400, Санкт-Петербург – 50/300, Челябинск – 100/200. После нажатия кнопки “Сохранить” в папке Debug приложения появился файл New.docx, такого вида:

end

Как видно из статьи, изменения вышедшие в C# 4.0 - намного сокращают код необходимый для работы с СОМ, в частности при работе с Office.

Александр Кобелев aka Megano