БлогNot. С#: работа с Microsoft Office из приложения Windows Forms

С#: работа с Microsoft Office из приложения Windows Forms

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

Проект Lab7_1. Подключение Word для проверки орфографии документа и программного формирования отчёта.

Создадим новый проект Windows Forms, на форме расположим поле ввода richTextBox1 и растянем его на всю форму (Dock = Fill).

Добавим на форму стандартные файловые диалоги openFileDialog1, saveFileDialog1.

Добавим верхнее меню menuStrip1 с пунктами Файл - Создать, Файл - Открыть, Файл - Сохранить, Файл - Выход, запрограммируем их (все методы созданы в режиме конструктора):

  private void создатьToolStripMenuItem_Click (object sender, EventArgs e) {
   richTextBox1.Clear ();
  }
  
  private void открытьToolStripMenuItem_Click (object sender, EventArgs e) {
   openFileDialog1.FileName = "";
   openFileDialog1.Filter = myFilter;
   if (openFileDialog1.ShowDialog() == DialogResult.OK) {
    openFileDialog1.FileName.Trim ();
    if (openFileDialog1.FileName.Length > 0) {
     try {
      System.IO.Stream myStream = null;
      if ((myStream = openFileDialog1.OpenFile())!=null) {
       richTextBox1.LoadFile (openFileDialog1.FileName);
       myStream.Close ();
      }
     }
     catch (Exception) {
      MessageBox.Show ("Не могу открыть файл "+
       System.IO.Path.GetFileName(openFileDialog1.FileName)+
       ". Проверьте его имя, права доступа и формат (нужен RTF)",
       "Ошибка",MessageBoxButtons.OK,MessageBoxIcon.Warning);
      return;
     }
    }
    else {
     MessageBox.Show ("Не могу открыть файл, " + 
      "переданное имя пусто",
      "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
     return;
    }
   }
  }

  private void сохранитьToolStripMenuItem_Click (object sender, EventArgs e) {
   saveFileDialog1.FileName = "";
   saveFileDialog1.Filter = myFilter;
   saveFileDialog1.DefaultExt = "rtf";
   if (saveFileDialog1.ShowDialog () == DialogResult.OK) {
    try {
     richTextBox1.SaveFile(saveFileDialog1.FileName,RichTextBoxStreamType.RichText);
     /*
     //Вариант кода для записи через поток (для содержимого txt)
     System.IO.Stream myStream = saveFileDialog1.OpenFile ();
     System.IO.StreamWriter writer = new System.IO.StreamWriter(myStream);
     writer.Write (richTextBox1.Text);
     writer.Flush ();
     writer.Close ();
     */
    }
    catch (Exception) {
     MessageBox.Show ("Не могу сохранить файл " +
      System.IO.Path.GetFileName (openFileDialog1.FileName) +
      ". Проверьте его имя и права доступа",
      "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
     return;
    }
   }
  }
  private void выходToolStripMenuItem_Click (object sender, EventArgs e) {
   Close ();
  }

Здесь строка MyFilter описана в классе формы и проинициализирована в её конструкторе:

  private String myFilter;
  public Form1 () {
   InitializeComponent ();
   myFilter = "Файлы *.rtf|*.rtf|Все файлы *.*|*.*";
  }

При открытии проекта, в который уже добавлены дополнительные ссылки проверьте в списке Обозреватель решений – Ссылки, что они активны, при необходимости удалите неработающую ссылку на COM-объект и добавьте её заново.

Проверьте, что в свойствах ссылки включена опция ”Внедрить типы взаимодействия”.

Для добавления в проект готовой библиотеки делаем следующее.

В обозревателе решений щёлкнуть правой кнопкой мыши на выделенном жирным имени проекта, выбрать команды Добавить – Ссылка, переключиться к списку "COM", отметить в нём чекбокс на имени объекта "Microsoft Word 12.0 Library" (или другой версии) и нажать OK.

В списке "Ссылки" обозревателя появился объект "Microsoft.Office.Interop.Word", и мы можем использовать в проекте возможности соответствующей библиотеки.

Добавим пункт верхнего меню Правка - Проверить и реализуем для него следующий код (метод создан из конструктора):

  private void проверитьToolStripMenuItem_Click (object sender, EventArgs e) {
   var Word = new Microsoft.Office.Interop.Word.Application ();
    //Создаём приложение Word
   var Doc = Word.Documents.Add();
    //Создаём новый документ MS Word
   string Test = "Сабака бываит кучачей.";
   Doc.Words.First.InsertBefore (Test);
   Doc.Words.First.InsertBefore (richTextBox1.Text);
    //Вводим в документ MS Word свою строку + текст из текстового поля
   richTextBox1.Text = Test + richTextBox1.Text;
   Doc.CheckSpelling (); //Проверяем орфорграфию
   String Corrected = Doc.Content.Text; //Получаем текст документа
   richTextBox1.Text = Corrected;  //Помещаем текст назад
   MessageBox.Show ("Выполнено. Сейчас будет закрыт Word");
   try { 
    Word.Documents.Close (false); //Закрываем Word без сохранения
    Word.Quit (); 
    Word = null;
   }
   catch (System.Runtime.InteropServices.COMException) {
    return;
   }
  }

Обратите внимание, что при выборе пункта меню ”Проверка” будет вызвано стандартное окно Word ”Проверка орфографии” и исправления возможных ошибок выполняются именно там.

Пока что мы не формировали текст документа Word программно. Сделаем это, добавив к меню приложения пункт Правка - Список ошибок и запрограммировав его (метод создан из конструктора):

  private void списокОшибокToolStripMenuItem_Click (object sender, EventArgs e) {
   var Word = new Microsoft.Office.Interop.Word.Application ();
   Microsoft.Office.Interop.Word.Document document = 
    new Microsoft.Office.Interop.Word.Document ();
   document.Activate ();
   document.Content.Text += richTextBox1.Text; //Вводим текст прямо в документ
   Word.Visible = true; //Делаем Word видимым
   char [] separators = new char [] { ' ', '\t', '\r' }; //Разделители слов
   var words = richTextBox1.Text.Split //Разбиваем текст на слова
    (separators, StringSplitOptions.RemoveEmptyEntries);
   List <string> errors = new List<string> (); //Список ошибок
   foreach (string wd in words) {
    if (!Word.CheckSpelling (wd)) { //Формируем список ошибок
     errors.Add (wd);
    }
   }
   foreach (string wd in errors) { //Пишем список ошибок в документ
    document.Content.Text += wd + "\n";
   }
   object filename = @"errors.docx"; //Имя для файла
   var missing = Type.Missing; //Переменная с "пустым" значением
   try { //Сохраняем и закрываем документ
    document.SaveAs (ref filename); 
    string fullname = document.FullName;
    document.Close (ref missing, ref missing, ref missing);
    document = null;
    Word.Quit (ref missing, ref missing, ref missing);
    Word = null;
    MessageBox.Show ("Отчёт об ошибках записан в файл " + fullname);
   }
   catch (System.Runtime.InteropServices.COMException ex) {
    MessageBox.Show ("Произошла ошибка работы с Word:\n" + ex.Message);
    return;
   }
  }

Другие примеры программной вставки текста в документ Word можно посмотреть, например, по этой ссылке.

Добавим к приложению пункт меню Правка - Записать отчёт и реализуем следующий код (обработчик события создан из конструктора):

  private void записатьОтчётToolStripMenuItem_Click (object sender, EventArgs e) {
   //Код для формирования таблицы и передачи её в Word:
   String [][] MyData = { 
    new String [] { "Мастер Безенчук", "101-22-33" } ,
    new String [] { "Фирма \"Нимфа\"", "310-00-47" } ,
    new String [] { "Ипполит Матвеевич", "222-13-15" }
   };
   int Size = MyData.Length;
   var Word1 = new Microsoft.Office.Interop.Word.Application ();
   Word1.Visible = true;
   var Doc = Word1.Documents.Add ();

   Object FileName = @"report.docx"; //Подготовили имя файла для сохранения
   MessageBox.Show ("Документ создан и будет сохранён как " + FileName +
    "\nПроверьте, что в Word нет активных модальных окон, которые могут помешать работе.");

   Word1.Selection.TypeText ("ТАБЛИЦА ТЕЛЕФОНОВ");
    //Вводим текст в документ MS WORD с текущей позиции
   Word1.Selection.InsertParagraph ();
   Word1.Selection.InsertAfter (String.Format ($"Всего: {Size}"));
    //Добавляем параграф и текст в выделение
   object objUnit = Microsoft.Office.Interop.Word.WdUnits.wdStory;
   object missing = System.Reflection.Missing.Value;
   Word1.Selection.EndKey (ref objUnit, ref missing);
    //Программно переходим к концу выделения и создаём таблицу
   Object t1 = Microsoft.Office.Interop.Word.WdDefaultTableBehavior.wdWord9TableBehavior;
   //Параметр, указывающий, показывать ли границы ячеек
   Object t2 = Microsoft.Office.Interop.Word.WdAutoFitBehavior.wdAutoFitContent;
   //Параметр, указывающий будет ли приложение Word автоматически изменять 
   //размер ячеек в таблице для подгонки содержимого
   Word1.ActiveDocument.Tables.Add (Word1.Selection.Range, Size, 2, t1, t2);
    //Добавили таблицу из Size строк и 2 столбцов
   Word1.ActiveDocument.Tables [1].Range.Font.Size = 12;
    //Настроили шрифт. Нумерация таблиц с единицы!
   Word1.ActiveDocument.Tables [1].set_Style ("Сетка таблицы");
    //Настроили стиль, имена стилей зависят от локали!
   for (int i = 1; i <= Size; i++) { // Заполняем таблицу по ячейкам
    Microsoft.Office.Interop.Word.Cell cell = Word1.ActiveDocument.Tables [1].Cell (i, 1);
    cell.Range.Text = MyData [i - 1] [0];
    cell = Word1.ActiveDocument.Tables [1].Cell (i, 2);
    cell.Range.Text = MyData [i - 1] [1];
   }

   Object t3 = Microsoft.Office.Interop.Word.WdUnits.wdLine;
   //Назначаем единицы измерения в документе приложения MS Word
   Object LastString = Size;
    //Параметр, указывающий на последнюю строку в документе MS Word
   Object t = Type.Missing;
    //Пустой параметр
   Word1.Selection.MoveDown (t3, LastString, t);
    //Перевести текущую позицию (Selection) за пределы таблицы,
    //чтобы здесь вывести какой-либо текст
   Word1.Selection.TypeText ("Какой-либо текст после таблицы");
   try { //Сохраняем документ
    Word1.Documents[1].SaveAs(FileName);
   }
   catch (System.Runtime.InteropServices.COMException ex) {
    MessageBox.Show ("Произошла ошибка работы с Word:\n" + ex.Message);
    return;
   }
  }

При выборе пункта меню должен сохраниться документ Word со сформированным отчётом report.docx, папку, в которой сохранён документ, можно узнать из окна с сообщением пункта меню Правка - Список ошибок.

Следует понимать, что, если команда недоступна в Word, она не будет выполнена и программно (например, после запуска Word активизировалось модальное окно "Мастер активации"), а имена стилей, такик как "Сетка таблицы 1", могут зависеть от локали установленной версии офиса. Весь подобный код (а не только попытку сохранения файла) следует обрамлять блоками try - catch.

 Скачать пример Lab7_1 в архиве .zip с проектом C# Visual Studio 2019 (14 Кб)

Проект Lab7_2. Обращение к функциям Excel для выполнения расчётов и программное чтение файлов Excel.

Создадим новый проект Windows Forms, на форме расположим таблицу dataGridView1 и растянем её на всю форму (Dock = Fill).

Аналогично Lab7_1, мы добавили к проекту ссылку на COM-объект Microsoft Excel 14.0 Object Library (или вашей версии вместо 14). Посмотреть какие возможности Excel стали доступны проекту можно, нажав комбинацию клавиш Ctrl+Alt+J (или открыв Обозреватель объектов другим способом) и раскрыв список Microsoft.Office.Interop.Excel. Например, в перечне WorksheetFunction будут перечислены доступные функции Excel.

Добавим на форму стандартные файловые диалоги openFileDialog1, saveFileDialog1 с фильтром вида

  private String myFilter;
  public Form1 () {
   InitializeComponent ();
   myFilter = "Файлы Excel|*.xlsx; *.xls";
  }

Добавим верхнее меню menuStrip1 с пунктами Файл - Создать, Файл - Открыть, Файл - Сохранить, Файл - Экспорт, Файл - Выход, запрограммируем их.

В меню Правка предусмотрим три команды для выполнения расчётов формулами Excel.

Команда "Создать" будет очищать имеющиеся данные и создавать заново пустую таблицу 3*5 ячеек, устанавливая подписи столбцов и строк как в Excel (методы создан в режиме конструктора):

  private void создатьToolStripMenuItem_Click (object sender, EventArgs e) {
   //Очищаем прежнее содержимое, если оно есть:
   dataGridView1.DataSource = null;
   dataGridView1.Rows.Clear ();
   dataGridView1.Refresh ();
   //Создаём пуcтую таблицу 3*5:
   for (int j = 0; j < 5; j++) {
    var cell = new DataGridViewTextBoxCell { Value = "" };
    var col = new DataGridViewColumn (cell);
    dataGridView1.Columns.Add (col);
    dataGridView1.Columns [j].HeaderText = Char.ToString ((char) ( 'A' + j ));
    dataGridView1.Columns [j].HeaderCell.Style.Alignment = 
     DataGridViewContentAlignment.MiddleCenter;
    dataGridView1.Columns [j].HeaderCell.Style.Font = 
     new Font ("Arial", 12F, FontStyle.Bold, GraphicsUnit.Pixel);
   }
   dataGridView1.RowHeadersWidth = 60;
   for (int i = 0; i < 3; i++) {
    dataGridView1.Rows.Add ();
    dataGridView1.Rows [i].Height = 25;
    dataGridView1.Rows [i].HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleLeft;
    dataGridView1.Rows [i].HeaderCell.Value = (i+1).ToString();
   }
  } 

Рекомендуемый компанией Microsoft путь работы с документом Excel - использование источника данных OLE DB, поэтому подключим к проекту соответствующее пространство имён:

using System.Data.OleDb;

Заметим, что без установленного на компьютере программного обеспечения Micosoft Office работа с Excel возможна, например, при подключении к проекту пакета IronXL (являющегося платным). Существуют и бесплатные аналоги подобных библиотек, например, EPPlus, работающая с документами Open Office.

Команду "Открыть" мы научим открывать файлы Excel с жёсткого диска. Но сначала нам придётся "вручную" добавить в класс формы метод ReadExcel, выполняющий необходимую работу:

  private DataTable ReadExcel (string filepath, string listname) {
   DataTable dt = new DataTable();
   //Автоматически узнаём установленного поставщика DB:
   string provider = null;
   OleDbEnumerator enumerator = new OleDbEnumerator ();
   DataTable table = enumerator.GetElements ();
   if (table.Rows.Count < 2) {
    MessageBox.Show ("Недстаточно строк данных в файле",
      "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Error);
    return dt;
   }
   foreach (DataRow row in table.Rows) { 
    if (row ["SOURCES_NAME"].ToString () == "Microsoft.Jet.OLEDB.4.0") 
     provider = "Microsoft.Jet.OLEDB.4.0";
    if (row ["SOURCES_NAME"].ToString () == "Microsoft.ACE.OLEDB.12.0") 
     provider = "Microsoft.ACE.OLEDB.12.0";
    //возможно, понадобятся другие проверки
   }
   //Выполняем запрос на получение данных:
   DataSet ds = new DataSet ();
   string constring = @"Provider=" + provider +
    ";Data Source=" + filepath
    + ";Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;\"";
   //Берём старую версию 8.0, так как прочитали через Microsoft.Jet;
   //Может понадобиться корректировка строки запроса
   //HDR=NO - нет заголовков столбцов
   //настройка IMEX обрабатывает все данные как текст
   OleDbConnection con = new OleDbConnection (constring + "");
   string sqlquery = "Select * From ["+listname+"$]";
   OleDbDataAdapter da = new OleDbDataAdapter (sqlquery, con); 
   da.Fill (ds);
   dt = ds.Tables [0];
   da.Dispose ();
   con.Dispose ();
   return dt;
  }

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

Показанный листинг сработал при сохранении файла Excel в формат "Книга Excel 97-2003 (*.xls)". Для формата *.xlsx чтение средствами Microsoft.Jet.OLEDB.4.0 может привести, например, к тому, что данные читаются только при открытом в Excel документе. Рецепт - установка пакета Microsoft Access Database Engine от Microsoft и применение соединений Microsoft.ACE.OLEDB.12.0.

Теперь напишем обработчик команды Файл - Открыть, созданный из конструктора:

  private void открытьToolStripMenuItem_Click (object sender, EventArgs e) {
   openFileDialog1.Filter = myFilter;
   if (openFileDialog1.ShowDialog () == DialogResult.OK) {
    string fileExt = System.IO.Path.GetExtension (openFileDialog1.FileName);
    if (fileExt.CompareTo (".xls") == 0 || fileExt.CompareTo (".xlsx") == 0) {
     try {
      DataTable dtExcel = ReadExcel (openFileDialog1.FileName, "Лист1");
      dataGridView1.DataSource = null;
      dataGridView1.Columns.Clear ();
      dataGridView1.Refresh ();
      dataGridView1.DataSource = dtExcel;
      for (int j = 0; j < dataGridView1.Columns.Count; j++) {
       //устанавливаем заголовки заново
       dataGridView1.Columns [j].HeaderText = Char.ToString ((char) ( 'A' + j ));
       dataGridView1.Columns [j].HeaderCell.Style.Alignment = 
        DataGridViewContentAlignment.MiddleCenter;
       dataGridView1.Columns [j].HeaderCell.Style.Font = 
        new Font ("Arial", 12F, FontStyle.Bold, GraphicsUnit.Pixel);
      }
     }
     catch (Exception ex) {
      MessageBox.Show ("Ошибка чтения файла Excel:\n" + ex.Message.ToString ());
     }
    }
    else {
     MessageBox.Show ("Выбирайте только файлы .xls или .xlsx", 
      "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
   }
  }

Для сохранения текущих данных нашей таблицы в файл Excel используем диалог сохранения файла и возможности объектов Excel.Workbook, Excel.Worksheet (функция создана из конструктора):

  private void сохранитьToolStripMenuItem_Click (object sender, EventArgs e) {
   saveFileDialog1.Filter = myFilter;
   if (saveFileDialog1.ShowDialog () == DialogResult.OK) {
    string fileExt = System.IO.Path.GetExtension (saveFileDialog1.FileName);
    if (fileExt.CompareTo (".xls") == 0 || fileExt.CompareTo (".xlsx") == 0) {
     try {
      Microsoft.Office.Interop.Excel.Application xlApp =
       new Microsoft.Office.Interop.Excel.Application (); //Excel
      Microsoft.Office.Interop.Excel.Workbook xlWB; //рабочая книга              
      Microsoft.Office.Interop.Excel.Worksheet xlSht; //лист Excel   
      xlWB = xlApp.Workbooks.Add (); //создаём рабочую книгу Excel 
      xlSht = xlApp.ActiveSheet; //Лист1
      int colCount = dataGridView1.ColumnCount;
      int rowCount = dataGridView1.RowCount;
      for (int i = 0; i < rowCount; i++) {
       for (int j = 0; j < colCount; j++) {
        xlSht.Cells [i + 1, j + 1] = dataGridView1.Rows [i].Cells [j].Value;
       }
      }
      xlApp.Visible = false; //не отображается сам файл
      xlApp.UserControl = true;
      if (fileExt.CompareTo (".xls") == 0) {
       if (System.IO.File.Exists (saveFileDialog1.FileName))
        System.IO.File.Delete (saveFileDialog1.FileName);
       xlWB.SaveAs (saveFileDialog1.FileName, Microsoft.Office.Interop.Excel.XlFileFormat.xlExcel8); 
        //формат Excel 2003
      }
      else {
       // ЕСЛИ НУЖНО СОХРАНИТЬ ФАЙЛ В ФОРМАТЕ EXCEL 2007 (XLSX) 
       if (System.IO.File.Exists (saveFileDialog1.FileName))
        System.IO.File.Delete (saveFileDialog1.FileName);
       xlWB.SaveAs (saveFileDialog1.FileName, 
        Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookDefault); //формат Excel 2007 и выше
      }
      xlWB.Close (false); //false - закрыть рабочую книгу не сохраняя изменения
      xlApp.Quit (); //закрываем приложение Excel
      MessageBox.Show ("Файл " + saveFileDialog1.FileName + " сохранён.", "Сохранение файла", 
       MessageBoxButtons.OK, MessageBoxIcon.Information);
     }
     catch (Exception ex) {
      MessageBox.Show ("Ошибка записи файла Excel:\n" + ex.Message.ToString ());
     }
    }
    else {
     MessageBox.Show ("Выбирайте только файлы .xls или .xlsx",
      "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
   }
  }

Заметим, что показанный выше код, где данные из DataGridView собираются "вручную" двойным циклом, может оказаться медленным при увеличении размера таблицы. Правильнее было бы загружать данные из DataGridView в двумерный массив, а затем "одной строкой кода" выгружать их на лист Excel. Ниже в показано подобное решение, реализованное как созданный из конструктора обработчик пункта меню Файл - Экспорт.

  private void экспортToolStripMenuItem_Click (object sender, EventArgs e) {
   if (dataGridView1.Rows.Count < 1 || dataGridView1.Columns.Count < 1) {
    MessageBox.Show ("Нет данных для выгрузки в Excel!",
     "Внимание", MessageBoxButtons.OK, MessageBoxIcon.Information);
    return;
   }
   if (MessageBox.Show ("Выгрузить найденные строки в Excel?", "Вопрос",
    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) {
    return;
   }
   Microsoft.Office.Interop.Excel.Application xlApp;
   Microsoft.Office.Interop.Excel.Workbook xlWB;
   Microsoft.Office.Interop.Excel.Worksheet xlSht;
   Microsoft.Office.Interop.Excel.Range Rng;
   xlApp = new Microsoft.Office.Interop.Excel.Application ();
   xlWB = xlApp.Workbooks.Add ();
   xlSht = xlWB.Worksheets [1]; //первый по порядку лист в книге Excel
   int RowsCount = this.dataGridView1.RowCount;
   int ColumnsCount = this.dataGridView1.ColumnCount;
   object [,] arrData = new object [RowsCount, ColumnsCount];
   for (int j = 0; j < RowsCount - 1; j++)
    for (int i = 0; i < ColumnsCount; i++)
     if (j != this.dataGridView1.NewRowIndex)
      arrData [j, i] = dataGridView1.Rows [j].Cells [i].Value.ToString ();
   //выгрузка данных на лист Excel
   xlSht.Range ["A2"].Resize 
    [arrData.GetUpperBound (0) + 1, arrData.GetUpperBound (1) + 1].Value = arrData;
   //переносим названия столбцов в Excel файл
   for (int j = 0; j < this.dataGridView1.Columns.Count; j++)
    xlSht.Cells [1, j + 1] = this.dataGridView1.Columns [j].HeaderCell.Value.ToString ();
   //украшательство таблицы
   xlSht.Cells [1, 1].CurrentRegion.Borders.LineStyle =
    Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous; //границы
   xlSht.Rows [1].Font.Bold = true;
   xlSht.Range ["A:H"].EntireColumn.AutoFit ();
   //отображаем Excel
   xlApp.Visible = true;
  }

Добавим к приложению обработчик пункта меню Правка - Расчёт 1 и проверим работу функции Excel, например, вычислим площадь круга с радиусом R, указанным в ячейке A1 табличной компоненты:

  private void расчёт1ToolStripMenuItem_Click (object sender, EventArgs e) {
   double R = 1.0;
   try {
    R = Double.Parse (dataGridView1.Rows [0].Cells [0].Value.ToString ().Trim());
   }
   catch (NullReferenceException) {
    MessageBox.Show ("Ячейка A1 пуста",
     "Сообщение", MessageBoxButtons.OK, MessageBoxIcon.Information);
    return;
   }
   catch (Exception) {
    MessageBox.Show ("Не могу получить числовое значение из ячейки A1",
     "Сообщение", MessageBoxButtons.OK, MessageBoxIcon.Information);
    return;
   }
   var XL = new Microsoft.Office.Interop.Excel.Application ();
   double S = XL.WorksheetFunction.Pi () * XL.WorksheetFunction.Power (R, 2);
    //получили результат выполнения функции Excel
   dataGridView1.Rows [1].Cells [0].Value = "Радиус = " + Math.Round (S, 3).ToString ();
   XL.Quit ();
  }

Пункт меню Правка - Расчёт 2 показывает, как реализовать вычисления, связанные с векторными или матричными операциями Excel, например, мы решим систему алгебраических линейных уравнений.

Для простоты не будем реализовывать код чтения данных из DataGridView аналогичный написанному выше, а программно выведем матрицу размерностью 3*3 в ячейки A1:C3, вектор правой части - в ячейки E1:E3, а результат будет выводиться в ячейки D1:D3.

Пример также наглядно показывает особенности использования двумерного контекста при работе с функциями Excel.

  private void расчёт2ToolStripMenuItem_Click (object sender, EventArgs e) {
   double [,] A = new double [3, 3] { { 1, 12, 3}, { -1, 1, 5}, { 0, 1, 1} };
   double [,] B = new double [3, 1]  { {6}, {3}, {5} };
   // Чтобы ответ приобрел индексированные свойства, нужен 2-мерный массив
   создатьToolStripMenuItem_Click (this, e); //очищаем лист, будет пустая таблица 3*5
   for (int j = 0; j < 3; j++) //переписываем данные на лист
    for (int i = 0; i < 3; i++)
     dataGridView1.Rows [j].Cells [i].Value = A [j, i].ToString ();
   for (int j = 0; j < 3; j++)
    dataGridView1.Rows [j].Cells [4].Value = B [j, 0].ToString ();
   var XL1 = new Microsoft.Office.Interop.Excel.Application ();
   double det_A = XL1.Application.WorksheetFunction.MDeterm (A);
    //нашли определитель матрицы A
   this.Text = "Det(A)=" + Convert.ToString (det_A);
    //вывели определитель в заголовок окна - просто некуда было его девать :)
   if (det_A != 0) {
    object oA = XL1.Application.WorksheetFunction.MInverse (A);
    Object [,] Xd = XL1.Application.WorksheetFunction.MMult (oA, B);
    for (int j = 1; j <= 3; j++) //В Xd нумерация с единицы!
     dataGridView1.Rows [j-1].Cells [3].Value = Xd [j, 1].ToString ();
   }
   else {
    MessageBox.Show ("Определитель = 0, нечего считать", "Сообщение", 
     MessageBoxButtons.OK, MessageBoxIcon.Information);
   }
   XL1.Quit ();
  }

Попробуйте самостоятельно реализовать код для проверки полученного решения функцией Excel МУМНОЖ (MMult) над массивами A и Xd и определения погрешности решения.

В отличие C++/CLI (Visual C++) при работе с Excel средствами C# нет неудобств, связанных с тем, что в С++ отсутствуют опциональные параметры функций и значения по умолчанию для них. Так, функция Max, согласно документации, имеет 30 параметров, но на C# нам не придётся указывать их все. В качестве примера приведём обработчик пункта меню Правка – Расчёт 3, вычисляющий стандартной функцией Excel максимальное из 9 значений, введённых в левые верхние ячейки компонента dataGridView1. На этот раз будем перехватывать исключения при получении данных в массив A.

  private void расчёт3ToolStripMenuItem_Click (object sender, EventArgs e) {
   const int height = 3, width = 3;
   double [,] A = new double [height, width];
   for (int i = 0; i < height; i++)
    for (int j = 0; j < width; j++) {
     A [i, j] = double.MinValue;
     try {
      A [i, j] =
      Double.Parse (dataGridView1.Rows [i].Cells [j].Value.ToString ());
     }
     catch (Exception) {
      A [i, j] = 0;
     }
   }
   var XL1 = new Microsoft.Office.Interop.Excel.Application ();
   double Res = XL1.Application.WorksheetFunction.Max (A);
   String Result = "Max(A1:C3)="+Convert.ToString(Res);
   MessageBox.Show (Result,"Ответ",MessageBoxButtons.OK,MessageBoxIcon.Information);
   XL1.Quit ();
  }

 Скачать пример Lab7_2 в архиве .zip с проектом C# Visual Studio 2019 (15 Кб)

Проект Lab7_3. Работа с базами данных средствами Access.

Работа с БД делается по технологии ADO (с формированием Connecting String), поддерживаются MS SQL и Access. Информация из БД кэшируется в DataSet, который обеспечивает приложение данными.

Работа с системными источниками данных поддерживается только в "полных" версиях Visual Studio, но не Express. Поэтому далее рассмотрим работу с СУБД Access, предполагая, что на компьютере установлен Microsoft Office версий 2007 или выше.

Я проверял это решение только в Access 2007 на работе, так как дома данного приложения не держу. Работа с Access более старших версий может потребовать иных настроек подключения.

Подготовим тестовый файл в Access:

  • имя файла = db1.accdb (начиная с версии офиса 2007, ранее db1.mdb);
  • имя таблицы = Table1.
  • в таблице имеется 3 поля типов Счетчик (имя Id), Текст (имя Name1), Числовой (имя Number). Внесём несколько записей и сохраним файл в папке проекта.
Добавим в Studio отключённые по умолчанию компоненты:
  • правая кнопка на списке "Данные" Панели элементов, команда Выбрать элементы..., вкладка Компоненты .NET Framework, добавить OleDbConnection, OleDbDataAdapter (появятся в группе "Данные").

Если у вас версия Express или другая сборка Studio, которая грешит "глюками" с отображением списка компонентов, попробуйте сбросить панель элементов в исходное состояние (тоже правой кнопкой мыши на списке) и повторить операцию.

Перенесём компонент OleDbDataAdapter на форму, появится мастер подключения. Нажав кнопку "Создать подключение", выберем источник данных "Файл базы данных Microsoft Access", кнопкой "Обзор" покажем на заранее подготовленный файл Access, при запросе строки SQL введём

select * from Table1

Проверим, что на форме создалось подключение OleDbConnection.

В норме OleDbDataAdapter должен создать команды для действий SELECT, INSERT, UPDATE и DELETE. Но, возможно, в зависимости от версии офиса и Studio, придётся дополнительно настраивать свойства oleDbDataAdapter1 - SelectCommand - CommandText и oleDbDataAdapter1 - UpdateCommand - CommandText.

Это можно сделать как в диспетчере свойств, например, написав в свойстве oleDbDataAdapter1 - SelectCommand - CommandText:

SELECT Table1.* FROM Table1

так и программно (настраиваем UpdateCommand):

   System.Data.OleDb.OleDbCommand command = new System.Data.OleDb.OleDbCommand (
   "UPDATE Table1 SET Id = ?, Name1 = ?, Number = ? WHERE Id = ?", oleDbConnection1);
   command.Parameters.Add (
    "Id", System.Data.OleDb.OleDbType.UnsignedInt, 32, "Id");
   command.Parameters.Add (
    "Name1", System.Data.OleDb.OleDbType.VarChar, 50, "Name1");
   command.Parameters.Add (
    "Number", System.Data.OleDb.OleDbType.Decimal, 10, "Number");
   oleDbDataAdapter1.UpdateCommand = command;

Возможно (как альтернатива) настроить все команды "автоматически", для этого щелкните по стрелочке в правом верхнем углу значка oleDbDataAdapter1 и выберите "Настроить адаптер данных". Помешать автонастройке может отсутствие запущенного сервера SQL.

Добавим на форму следующие компоненты:

  • DataGridView, DataSet - для обеспечения подключения.
  • BingingNavigator - для навигации по БД.
  • BingingSource - для связи источника данных с навигатором.

В свойствах DataSet выбрать свойство Tables, добавить одну таблицу Table1, в окне Редактор коллекции столбцов (вызывать из свойства Columns таблицы dataGridView1) добавить 3 столбца с именами как в файле Access (Id, Name1, Number). Для каждого столбца указать свойство Name - имя столбца из Access.

В свойствах bingingSource1 указать DataSource = dataSet1, DataMember = Table1.

Для bindingNavigator указать bindingSource = bindingSource1. Для dataGridView1 указать DataSource = bindingSource1 (в нашем случае будет неверно DataSource=dataSet1, DataMember=Table1).

Для oleDbDataAdapter1 указать свойство SelectCommand - Connection = oleDbConnection1.

По некоторому событию (например, по загрузке формы) получим данные из таблицы БД:

   oleDbDataAdapter1.Fill (dataTable1);

Перед закрытием формы (FormClosing) сохраним изменения в таблице Access:

   oleDbDataAdapter1.Update (dataTable1);
    //Свойство (Name) при "ручном" добавлении таблицы в dataSet1
    //осталось = dataTable1 независимо от имени таблицы (TableName)

или (надёжнее):

   oleDbDataAdapter1.Update (dataSet1.Tables["Table1"]);

Возможно, в зависимости от версии офиса, придётся дополнительно настраивать команды oleDbDataAdapter1 - SelectCommand - CommandText и oleDbDataAdapter1 - UpdateCommand - CommandText (как сказано выше).

Пример кода с программным коннектом к базе данных Access:

  private void запросToolStripMenuItem_Click (object sender, EventArgs e) {
   if (openFileDialog1.ShowDialog () != DialogResult.OK) {
    return;
   }
   string filename = openFileDialog1.FileName;
   dataSet1.Tables.Add ("Search");
   dataSet1.Tables["Search"].Columns.Add ("Name1");
   String  SQL = "Select Name1 from table1 where (Name1='Иванов')";
    //Введите свой запрос
   System.Data.OleDb.OleDbConnection Connection = null;
   string fileExt = System.IO.Path.GetExtension (openFileDialog1.FileName);
   if (fileExt.CompareTo (".mdb") == 0) {
    //Для старого формата Access
    Connection = new System.Data.OleDb.OleDbConnection (
     "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + filename);
   }
   else {
    //Для нового формата Access
    Connection = new System.Data.OleDb.OleDbConnection (
     "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename);
   }
   Connection.Open ();
   var Command = new System.Data.OleDb.OleDbCommand (SQL, Connection);
   //Есть путь:
   //int i=Command.ExecuteNonQuery(); Connection.Close();
   var Reader = Command.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
   while (Reader.Read ()) { 
    dataSet1.Tables["Search"].Rows.Add (Reader.GetValue (0)); 
   }
   Reader.Close ();
   bindingSource1.DataSource = dataSet1;
   bindingSource1.DataMember = "Search";
   bindingNavigator1.BindingSource = bindingSource1;
   bindingNavigator1.Update ();
   dataGridView1.DataSource = bindingSource1;
   oleDbDataAdapter1.Fill (dataSet1.Tables["Search"]);
  }

Для диалога открытия файла openFileDialog1 установлено свойство Filter = Файлы Access|*mdb; *.accdb .

 Скачать пример Lab7_3 в архиве .zip с проектом C# Visual Studio 2019 (37 Кб)

Задание по теме может быть примерно таким: для своего варианта одной из работ со списком или таблицей реализовать сохранение отчёта в выбранном формате файла Microsoft Office.

27.04.2023, 10:30 [877 просмотров]


теги: c# учебное программирование excel studio сервер access word

К этой статье пока нет комментариев, Ваш будет первым