Инструментарий:
- Microsoft Visual Studio 2010
- Microsoft Office 2010 Professional Beta
Исходники к статье
Все программы работают с данными и довольно часто их приходится выводить на печать, или отправлять по электронной почте, к тому же, бывает необходимо не просто послать сухие списки, а выделить какую-нибудь информацию, повышая ее важность
при диагональном прочтении, или создать красивые графики с таблицами для любимого начальства и все это обязательно на фирменном бланке.
Для решения этой задачи могут пригодиться шаблоны WORD, ведь в большинстве своем для каждого случая существует свой, заранее разработанный бланк, в который просто подставляются текущие данные.
Предположим, существует некая компания «Ключи и Отвертки», которая занимается продажами обозначенных инструментов, и менеджеры компании хотят видеть ежедневный отчет о продажах в своих офисах.
Начнем с простого шаблона, который будет представлять из себя фирменный бланк компании и единственное поле для вставки данных. Создадим новый WORD документ и уменьшим отступы (Page Layout -> Margins -> Narrow).
После этого развернем документ в альбомную ориентацию (Page Layout -> Orientation -> Landscape).
Далее создадим шапку (Insert -> Header –> Alpabet).
Напишем название. Далее впишем дополнительную информацию о компании и пометим ее курсивом с выравниванием по правому краю.
После создания шапки в документ автоматически был добавлен пустой подвал, перейти в редактирование которого можно просто перенеся туда курсор. Визуально отделим его от тела документа при помощи разделительной линии. (Insert –> Shapes -> Lines)
Далее, в подвале сделаем выравнивание по правому краю и создадим закладку (Bookmark), которую будем отыскивать в коде и на ее место вставлять данные (Insert -> Bookmark).
И назовем ее 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(); object oMissing = System.Reflection.Missing.Value;
Переменная oWord будет представлять процесс WINWORD.EXE в памяти, oMissing представляет из себя аргумент представляющий значение по умолчанию, который требуют методы из пространства Microsoft.Office.Interop, так как они не принимают
Null.
Так же определим событие формы Closing, в котором мы будем закрывать процесс WINWORD.EXE
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) { Word._Document oDoc = LoadTemplate(Environment.CurrentDirectory + "\screwdriver.dotx"); SetTemplate(oDoc); SaveToDisk(oDoc, Environment.CurrentDirectory + "\New.docx"); oDoc.Close(ref oMissing, ref oMissing, ref oMissing); }
Здесь все сводится к вызову трех методов LoadTemplate(),SetTemplate(), SaveToDisk(). Загружаем сохраненный шаблон, заполняем его данными с формы и сохраняем на диск, после чего закрываем документ.
Примечание:
Остерегайтесь использования Environment.CurrentDirectory так как значение может измениться в процессе работы.
static void Main(string[] args) { Console.WriteLine(Environment.CurrentDirectory); Environment.CurrentDirectory = "c:\"; Console.WriteLine(Environment.CurrentDirectory); }
Как показано выше мы можем сами изменить ее значение, в данном случае Environment.CurrentDirectory больше указывает не на каталог приложения, а на корень диска C.
Ниже приведен код вызванных методов:
private Word._Document LoadTemplate(string filePath) { object oTemplate = filePath; Word._Document oDoc = oWord.Documents.Add(ref oTemplate, ref oMissing, ref oMissing, ref oMissing); return oDoc; } private void SetTemplate(Word._Document oDoc) { object oBookMark = "AuthorName"; oDoc.Bookmarks.get_Item(ref oBookMark).Range.Text = autorTxtb.Text; } 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); }
Сигнатуры вызываемых методов из пространства имен 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) { Word._Document oDoc = LoadTemplate(Environment.CurrentDirectory + "\screwdriver.dotx"); SetTemplate(oDoc); PrintDoc(oDoc); object notSave = Word.WdSaveOptions.wdDoNotSaveChanges; oDoc.Close(ref notSave, ref oMissing, ref oMissing); }
Здесь все то же самое, что и в предыдущем случае, только вместо метода SaveToDisk вызывается метод PrintDoc и перед закрытием документа явно определяется, что документ не стоит сохранять. В предыдущем случае, при сохранении документа на диск, он
помечался как сохраненный и не требовалось явно указывать этот параметр.
private void PrintDoc(Word._Document oDoc) { oDoc.PrintOut(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 , ref oMissing, ref oMissing, ref oMissing); }
Метод PrintDoc просто вызывает метод PrintOut с параметрами по умолчанию.
Теперь добавим на шаблон таблицу 3 на 4, которая будет отображать продажи по городам.
Верхняя строчка обозначит ключи и отвертки, а первый столбик города: Москва, Санкт-Петербург, Челябинск. Разукрасим таблицу по своему вкусу.
Далее мы могли бы поставить шесть закладок (boomark), задать им уникальные имена и искать их как в предыдущем случае, но это слишком накладно. Можно поступить проще – выделить всю таблицу и добавить закладку на все выделение сразу.
Назовем новую закладку “SaleTable”.
Добавим на форму DataGrid, со свойством AutoGenerateColumns=»True»,
а в код новую переменную
DataTable table;
Которая будет являться DataContext для DataGrid
В конструкторе вызовем метод, который заполнит 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) { object oTableBookMark = bookmark; var tbl = oDoc.Bookmarks.get_Item(ref oTableBookMark).Range.Tables[1]; int tblRow = 0; int tblCell = 0; foreach (Word.Column col in tbl.Columns) { foreach (Word.Cell cell in col.Cells) { cell.Range.Text = (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)
и выставим все значения по нулям.
После чего выделим график на шаблоне (кликнув по краю графика) и создадим новую закладку “ChartBookmark”, далее сохраним изменения в шаблоне.
Добавим в метод SetTemplate() вызов метода SetChart(), который будет заполнять данными график.
private void SetTemplate(Word._Document oDoc) { ... SetChart(oDoc, "ChartBookmark", table); } private void SetChart(Word._Document oDoc, string bookmark, DataTable dataContext) { object oChartBookMark = bookmark; Word.Chart chart = oDoc.Bookmarks.get_Item(ref ChartBookMark).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.get_Range("B2", oMissing).FormulaR1C1 = (string)dataContext.Rows[1][1]; dataSheet.Cells.get_Range("B3", oMissing).FormulaR1C1 = (string)dataContext.Rows[1][2]; dataSheet.Cells.get_Range("C2", oMissing).FormulaR1C1 = (string)dataContext.Rows[2][1]; dataSheet.Cells.get_Range("C3", oMissing).FormulaR1C1 = (string)dataContext.Rows[2][2]; dataSheet.Cells.get_Range("D2", oMissing).FormulaR1C1 = (string)dataContext.Rows[3][1]; dataSheet.Cells.get_Range("D3", oMissing).FormulaR1C1 = (string)dataContext.Rows[3][2]; dataWorkbook.Close(oMissing, oMissing, oMissing); }
Теперь приложение полностью готово к работе, введем некоторые данные, чтобы провести функциональный тест. В строке автор я введу “Александр Кобелев” а значения продаж по городам у меня будут такие: Ключи/Отвертки Москва – 700/400,
Санкт-Петербург – 50/300, Челябинск – 100/200. После нажатия кнопки “Сохранить” в папке Debug приложения появился файл New.docx, такого вида:
Александр Кобелев aka Megano
Инструментарий:
• Microsoft Visual Studio 2010
• Microsoft Office 2010
исходники к статье
Данная статья является адаптацией более ранней статьи «Использование WORD шаблонов» с учетом новых возможностей вошедших в C# 4.0 таких как: именованные параметры, пропуск ключевого слова ref при работе с COM, индексируемые свойства и необязательные параметры. Вы можете сравнить количество кода с предыдущей статьей и увидеть, что кода сало много меньше, так же он стал более читабельным.
Все программы работают с данными и довольно часто их приходится выводить на печать, или отправлять по электронной почте, к тому же, бывает необходимо не просто послать сухие списки, а выделить какую-нибудь информацию, повышая ее важность при диагональном прочтении, или создать красивые графики с таблицами для любимого начальства и все это обязательно на фирменном бланке.
Для решения этой задачи могут пригодиться шаблоны WORD, ведь в большинстве своем для каждого случая существует свой, заранее разработанный бланк, в который просто подставляются текущие данные.
Предположим, существует некая компания «Ключи и Отвертки», которая занимается продажами обозначенных инструментов, и менеджеры компании хотят видеть ежедневный отчет о продажах в своих офисах.
Начнем с простого шаблона, который будет представлять из себя фирменный бланк компании и единственное поле для вставки данных. Создадим новый WORD документ и уменьшим отступы (Page Layout -> Margins -> Narrow).
После этого развернем документ в альбомную ориентацию (Page Layout -> Orientation -> Landscape).
Далее создадим шапку (Insert -> Header –> Alpabet).
Напишем название. Далее впишем дополнительную информацию о компании и пометим ее курсивом с выравниванием по правому краю.
После создания шапки в документ автоматически был добавлен пустой подвал, перейти в редактирование которого можно просто перенеся туда курсор. Визуально отделим его от тела документа при помощи разделительной линии. (Insert –> Shapes -> Lines)
Далее, в подвале сделаем выравнивание по правому краю и создадим закладку (Bookmark), которую будем отыскивать в коде и на ее место вставлять данные (Insert -> Bookmark).
И назовем ее 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, которая будет отображать продажи по городам. Верхняя строчка обозначит ключи и отвертки, а первый столбик города: Москва, Санкт-Петербург, Челябинск. Разукрасим таблицу по своему вкусу.
Далее мы могли бы поставить шесть закладок (boomark), задать им уникальные имена и искать их как в предыдущем случае, но это слишком накладно. Можно поступить проще – выделить всю таблицу и добавить закладку на все выделение сразу.
Назовем новую закладку “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)
и выставим все значения по нулям.
После чего выделим график на шаблоне (кликнув по краю графика) и создадим новую закладку “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, такого вида:
Как видно из статьи, изменения вышедшие в C# 4.0 — намного сокращают код необходимый для работы с СОМ, в частности при работе с Office.
Александр Кобелев aka Megano
Данная инструкция поможет вам начать работать с шаблонами Microsoft Word. Для начала работы вам понадобится компьютер с установленным на нем пакетом Microsoft Office. В данном случае используется профессиональная версия 2010 года. В Microsoft Word 2010 можно создать шаблон, сохраняя документ как DOTX-файл, DOT-файл или DOTM-файл (файл типа DOTM позволяет разрешить макросы в файле).
Шаблон — это тип документа, при открытии которого создается его копия. Например, бизнес-план — распространенный документ, часто создаваемый в Word. Вместо создания структуры бизнес-плана с самого начала можно использовать шаблон со стандартным макетом страницы, шрифтами, полями и стилями. Остается лишь открыть шаблон и заполнить документ соответствующим текстом и данными. При сохранении в виде файла .docx или .docm документ сохраняется отдельно от шаблона, на котором он основан.
В шаблоне можно сохранить рекомендуемые разделы или обязательный текст, а также элементы управления содержимым, такие как стандартный раскрывающийся список или специальная эмблема. Можно добавить защиту для раздела шаблона или применить к шаблону пароль, защищающий содержимое шаблона от изменений.
Начать можно с пустого документа и сохранить его как шаблон или создать шаблон на основе существующего документа или шаблона. Запустите Microsoft Word, перейдите во вкладку «Файл» и выберете пункт «Создать». Шаблоны Word для большинства видов документов можно найти на веб-сайте Office.com. В данном разделе вам предлагается выбрать готовые шаблоны или создать собственный. Выберете кнопку «Мои шаблоны».
В открывшемся окне, вам предложено создать новый документ, но стоит задача создать шаблон. Для этого найдите внизу окна пункт «Создать» и выберете «шаблон».
Теперь необходимо сохранить шаблон. Перейдите во вкладку «Файл» и выберете пункт «Сохранить как».
В открывшемся окне выберете директорию для сохранения, а тип файла поставьте «Шаблон Word 97-2003 (*.dot)». Данный формат позволит вам использовать шаблон как со старым офисом, версии 2003, так и с новыми версиями. Имя файла оставьте по умолчанию.
После того как вы сохранили шаблон, для добавления элементов управления содержимым необходимо отобразить вкладку «Разработчик». Перейдите «Файл»-«Параметры».
Откройте вкладку «Настройка ленты», в правой части окна, в списке «Настройка ленты» выберите пункт «Основные вкладки». Установите в списке флажок Разработчик и нажмите кнопку ОК.
После закрытия окна «Параметры Word», вы увидите дополнительную вкладку «Разработчик».
На вкладке «Разработчик» в группе «Элементы управления» нажмите кнопку «Режим конструктора».
Добавим к шаблону стандартные блоки. Стандартные блоки — это многократно используемые элементы содержимого или другие части документа, которые хранятся в коллекциях и в любое время доступны для повторного использования. Стандартные блоки также можно хранить и распространять с помощью шаблонов. Так как мы сохранили шаблон в старом формате 97-2003, вам будут доступны только формы предыдущих версий. В группе «Элементы управления», найдите и нажмите кнопку «Инструменты из предыдущих версий». В выпадающем меню вам будет предложен ряд блоков, доступных для вашего шаблона.
Добавьте несколько блоков, например элемент управления формы – поле, флажок, надпись.
После добавления, у вас получится вот такой вариант.
Для того чтобы мы могли обращаться к этим элементам, необходимо задать каждому уникальную закладку. Для этого выберете добавленный элемент – поле, сделайте клик правой клавишей мыши по нему и выберете из контекстного меню, пункт «Свойства».
Найдите внизу окна, поле «закладка» и введите новое уникальное имя, по которому вы будете обращаться к элементу. В нашем случае для исключения ошибок с закладками на русском, введите на английской раскладке, имя: T1, т.е сокращенно текстовое поле №1. Нажмите ОК.
Сохраните и закройте шаблон. Откройте Microsoft Visual Studio. В данной инструкции будет использована версия 2010. Создайте новый проект Windows Form. Добавим пространство имен Microsoft.Office.Interop.Word, необходимое нам для работы. Нажмите на клавиатуре клавишу F7, для перехода в листинг формы. В самом начале листинга, где объявляются пространства имен необходимые для работы проекта, добавьте еще одно:
using Word = Microsoft.Office.Interop.Word;
Далее необходимо добавить ссылку на библиотеку добавленного пространства имен, в наше решение. Для этого перейдите в «Обозреватель решений», откройте вкладку «Ссылки» и сделайте по ней клик правой клавишей мыши, в открывшемся контекстном меню, выберете пункт «Добавить ссылку…».
После открытия окна «Добавить ссылку», перейдите во вкладку «.Net» и найдите необходимый нам компонент Microsoft.Office.Interop.Word версии 12.0.0.0 и нажмите кнопку ОК.
После добавления ссылки на данную библиотеку, вы увидите ее название в обозревателе решений, в подразделе «Ссылки».
Поместите на форму два компонента, TextBox и Button:
Перейдите в конструктор формы и сделайте двойной клик по компоненту, вы перейдете в автоматически созданный метод button1_Click. Добавьте следующий код:
Word._Document oDoc = GetDoc(Environment.CurrentDirectory + "\Dot1.dot"); //Указываем путь к шаблону oDoc.SaveAs(FileName: Environment.CurrentDirectory + "\For_print.doc"); //Путь к заполненному шаблону oDoc.Close();
Класс Environment.CurrentDirectory возвращает полный путь к текущей рабочей папке, к месту в котором расположен ваш скомпилированный проект. Скопируйте ваш шаблон в папку отладки вашего проекта по умолчанию это папка Visual Studio 2010ProjectsWindowsFormsWordDotWindowsFormsWordDotbinDebug. Так же добавьте дополнительно в листинг формы два метода:
Word._Application oWord = new Word.Application(); private Word._Document GetDoc(string path) { Word._Document oDoc = oWord.Documents.Add(path); SetTemplate(oDoc); return oDoc; } private void SetTemplate(Word._Document oDoc) { oDoc.Bookmarks["T1"].Range.Text = textBox1.Text; }
Запустите приложение клавишей «F5» и введите в текстовое поле текст, который будет вставлен ив шаблон.
После ввода текста, нажмите на кнопку «Button1» вашей формы. Перейдите в директорию расположения вашего приложения и откройте, созданный вашим приложением по шаблону текстовый документ «For_print.doc».
Дальше по такому же принципу, вы можете нарисовать таблицу и с помощью этой инструкции ее заполнить. Для примера, это может выглядеть так:
Задача: вывести данные в документ Word. На самом деле это очень большая и необъятная тема, примерно как сам Word, 90% возможностей которого не используются обычными пользователями. Сузим до более простой и чаще встречающейся на практике задачи, с которой в своей время пришлось столкнуться мне самому: надо вывести красивую справку, договор, отчет или иной документ Word с добавлением данных из кода C#. Само собой должны поддерживаться версии Word до 2007, так что о новых форматах файлов придется забыть.
Для начала вспомним, что в Word есть такая замечательная вещь как шаблоны. Соответственно большую часть сложного оформления можно вынести в них и из кода открывать шаблон и вставлять данные в нужные места. Для начала ограничимся простыми строками (типовая задача в крупных предприятиях — вставка дат, цифр, фио и тому подобных вещей, договор на сумму такую-то, от такой-то даты с фио таким-то с параметрами объекта такими-то).
Задача на текущую статью: открыть из кода C# шаблон Word и что-то в него вставить. Шаблон в формате .dot приготовим заранее, в том же самом ворде. Для связи с ним будем использовать механизм COM Interoperability (сокращенно Interop), то есть запускать отдельный exe-процесс самого Word и через специальный интерфейс управлять им. Интерфейсы слава богу есть и находятся они в специальных библиотеках, поставляемых вместе с Office, но документация по ним крайне невнятная, поведение местами очень странное и не логичное. В версиях Visual Studio 2010 и выше возможности программирования Office расширены, но текущее руководство действительно и для 2008 студии.
Нам надо
1. Подключить нужные библиотеки
2. Открыть шаблон Word
3. Найти в нем нужное место
4. Вставить в него строку с информацией
1. Проект в студии у нас уже должен быть. В разделе Ссылки/References кликаем правой кнопкой, идем в «Добавить ссылку» и ищем Microsoft.Office.Interop.Word. В параметрах добавленной библиотеки ставим true в Копировать локально/Copy local, так как библиотеку надо копировать вместе с исполняемыми файлами проекта.
В код добавляем соответствующие using
using Word = Microsoft.Office.Interop.Word; using System.Reflection;
2. Теперь вам предстоит провести много времени с замечательным интерфейсом Word, который представляет сам текстовый редактор и его потроха в виде разнообразных обьектов. Сейчас важны два — Application и Document. Переменные для них по ряду не очевидных причин лучше объявлять через интерфейсы.
Word._Application application; Word._Document document;
Так же почти все функции Word требуют объектных параметров, даже если внутри них сидят простые строки и логические значения, так что лучше заранее сделать несколько оберток
Object missingObj = System.Reflection.Missing.Value; Object trueObj = true; Object falseObj = false;
Чтобы запустить Word и открыть в нем шаблон с диска (путь известен), потребуется примерно такой код
//создаем обьект приложения word application = new Word.Application(); // создаем путь к файлу Object templatePathObj = "путь к файлу шаблона";; // если вылетим не этом этапе, приложение останется открытым try { document = application.Documents.Add(ref templatePathObj, ref missingObj, ref missingObj, ref missingObj); } catch (Exception error) { document.Close(ref falseObj, ref missingObj, ref missingObj); application.Quit(ref missingObj, ref missingObj, ref missingObj); document = null; application = null; throw error; } _application.Visible = true;
Принципиально важны два момента
1. Мы создаем неуправляемый ресурс, который не соберет сборщик мусора — отдельный процесс в памяти с приложением Word, если мы его не закроем и не выведем на экран, он так и останется там висеть до выключения компьютера. Более того такие ворды могут накапливаться незаметно для пользователя, программист-то еще прибьет их вручную. Заботиться о высвобождения неуправляемого ресурса должен программист.
2. По умолчанию Word запускается невидимым, на экран его выводим мы.
Для начала рассмотрим самый простой и примитивный вариант — поиск и замена строки в документе Word. Некоторые программисты так и работают — ставят в шаблон текстовую метку вроде @@nowDate и заменяют ее на нужное значение.
Пришло время познакомится с фундаментом работы с Word — великим и ужасным объектом Range. Его суть сложно описать словами -это некоторый произвольный кусок документа, диапазон (range), который может включать в себя все что угодно — от пары символов, до таблиц, закладок и прочих интересных вещей. Не стоит путать его с Selection — куском документа, выделенным мышкой, который само собой можно конвертировать в Range. Соотвественно нам надо получить Range для всего документа, найти нужную строку внутри него, получить Range для этой строки и уже внутри этого последнего диапазона заменить текст на требуемый. И не стоит забывать, что документ может иметь сложную структуру с колонтитулами и прочей ересью, возможный универсальный метод для замены всех вхождений данной строки:
// обьектные строки для Word object strToFindObj = strToFind; object replaceStrObj = replaceStr; // диапазон документа Word Word.Range wordRange; //тип поиска и замены object replaceTypeObj; replaceTypeObj = Word.WdReplace.wdReplaceAll; // обходим все разделы документа for (int i = 1; i <= _document.Sections.Count; i++) { // берем всю секцию диапазоном wordRange = _document.Sections[i].Range; /* Обходим редкий глюк в Find, ПРИЗНАННЫЙ MICROSOFT, метод Execute на некоторых машинах вылетает с ошибкой "Заглушке переданы неправильные данные / Stub received bad data" Подробности: http://support.microsoft.com/default.aspx?scid=kb;en-us;313104 // выполняем метод поиска и замены обьекта диапазона ворд wordRange.Find.Execute(ref strToFindObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref replaceStrObj, ref replaceTypeObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing); */ Word.Find wordFindObj = wordRange.Find; object[] wordFindParameters = new object[15] { strToFindObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, replaceStrObj, replaceTypeObj, _missingObj, _missingObj, _missingObj, _missingObj }; wordFindObj.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, wordFindObj, wordFindParameters); }
Редкий глюк подробно описан здесь.
На самом деле это не самый лучший метод для вставки информации в документ, так как могут возникнуть сложности с уникальными именами для текстовых меток (если текст одной входит в начало другой, данный метод найдет ее и заменит), их совпадением с произвольным текстом и так далее.
Даже если нам надо найти (и например отформатировать) именно строку с текстом внутри документа, лучше всего выдать наружу найденный Range и уже с ним производить разные злодеяния. Получим примерно такой метод:
object stringToFindObj = stringToFind; Word.Range wordRange; bool rangeFound; //в цикле обходим все разделы документа, получаем Range, запускаем поиск // если поиск вернул true, он долже ужать Range до найденное строки, выходим и возвращаем Range // обходим все разделы документа for (int i = 1; i <= _document.Sections.Count; i++) { // берем всю секцию диапазоном wordRange = _document.Sections[i].Range; /* // Обходим редкий глюк в Find, ПРИЗНАННЫЙ MICROSOFT, метод Execute на некоторых машинах вылетает с ошибкой "Заглушке переданы неправильные данные / Stub received bad data" Подробности: http://support.microsoft.com/default.aspx?scid=kb;en-us;313104 // выполняем метод поиска и замены обьекта диапазона ворд rangeFound = wordRange.Find.Execute(ref stringToFindObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing); */ Word.Find wordFindObj = wordRange.Find; object[] wordFindParameters = new object[15] { stringToFindObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj }; rangeFound = (bool)wordFindObj.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, wordFindObj, wordFindParameters); if (rangeFound) { return wordRange; } } // если ничего не нашли, возвращаем null return null;
Простейшее решение проблемы уникальности текста (нужно нам найти Range слова Word, но внутри всего документа оно встречается десятки раз) — искать строку внутри строки, сначала найти уникальную строку, потом не уникальную внутри нее, неэстетично, но дешево, надежно и практично.
// оформляем обьектные параметры object stringToFindObj = stringToFind; bool rangeFound; /* Обходим редкий глюк в Find, ПРИЗНАННЫЙ MICROSOFT, метод Execute на некоторых машинах вылетает с ошибкой "Заглушке переданы неправильные данные / Stub received bad data" http://support.microsoft.com/default.aspx?scid=kb;en-us;313104 rangeFound = containerRange.Find.Execute(ref stringToFindObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing); */ Word.Find wordFindObj = containerRange.Find; object[] wordFindParameters = new object[15] { stringToFindObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj }; rangeFound = (bool)wordFindObj.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, wordFindObj, wordFindParameters); if (rangeFound) { return containerRange; } else { return null; }
Если строку надо просто заменить, то сойдет простейшее
_range.Text = "Это текст заменит содержимое Range";
Но так как Range является универсальный контейнером для любого куска документа Word, то его возможности неизмеримо шире, часть их будет рассмотрена в дальнейших заметках.
Если нам надо просто встать в начало документа (и что-то вставить уже туда):
object start = 0; object end = 0; _currentRange = _document.Range(ref start, ref end);
Сохранить документ на диск можно следующим образом
Object pathToSaveObj = pathToSaveString; _document.SaveAs(ref pathToSaveObj, Word.WdSaveFormat.wdFormatDocument, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj, ref _missingObj);
- Работаем с MS Word из C#, часть 0, класс и тестовый проект-пример WinForms
- Работаем с MS Word из C#, часть 1. Открываем шаблон, ищем текст внутри документа
- Работаем с MS Word из C#, часть 2. Вставляем текст на закладку и форматируем
- Работаем с MS Word из C#, часть 3. Работа с таблицами
- Работаем с MS Word из C#, часть 4. Обьединяем несколько файлов в один, считаем количество страниц
- Microsoft.Office.Interop.Word Namespace
- Range Interface
I am trying to create about 600 reports in Microsoft office Word. The documents are populated with data from a database, and images found on a local drive.
I have figured out, that I might create a Word Template project in visual studio 2010, and program the template, so that when you enter a single value (id-number), it automatically fills out the entire document.
I am quite confident that this is possible. the only problem is. How do I loop through all entries in the database, open a new document based on the template and set the id-value?
for(int i = 0; i < idnumbers.Count(); i++)
{
Word.Application app = new Word.Application();
Word.Document doc = app.Documents.Add(@"C:..WordGeneratorbinDebugWordTemplate.dotx");
//input the id-number below: HOW??
doc.SaveAs(FileName: @"c:temptest.docx");
}
The application is supposed to run only once, generating the reports, and it doesn´t have to be fast. It just has to be easy to develop.
The problem here is, that it seems that the DocumentBase object is not accessible outside the Word project. The substitute Microsoft.Office.Interop.Word.Document
does not have functionality like SelectContentControlsByTitle
that allows me to find and set my ContentControls
. And that is exactly what I need to do..
EDIT: This is what my code looks like now to insert the text into my field:
Word.Application app = new Word.Application();
Word.Document doc = app.Documents.Add(@"C:..test.dotx");
foreach (Word.ContentControl cc in doc.SelectContentControlsByTitle("MyCCTitle"))
{
cc.Range.Text += "1234";
}
doc.SaveAs(FileName: @"c:temptest.docx");
Then an eventhandler on my template on BeforeSave fills out the document based on the text in MyCCTitle-titled object.