Время на прочтение
9 мин
Количество просмотров 42K
В заметке предлагается набор классов C++ (работоспособность проверена в VS2008 и VS 2013; используется только С++03 и STL) для вывода в поток std::ostream данных с табличным форматированием. Распространяется «As Is».
st << "#" << "Property" << "Value" << "Unit";
enum {nr = 10};
for (int i = 0; i < nr; i++) {
st << i + 1 << "Prop" << i << "Unit";
}
Результат будет выглядеть так:
Уже в процессе подготовки заметки нашел похожий проект bprinter (подробного сравнения не проводил; прослеживается зависимость от Boost::Spirit, что не всегда удобно), который является аналогом моего файла
StreamTable.h
#ifndef __STREAM_TABLE_H
#define __STREAM_TABLE_H
#undef max
#undef min
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
//стратегия формирования единой таблицы
#define CRLF "n"
//стратегия построчной выгрузки таблицы
//#define CRLF std::endl
/**
* Прямоугольная таблица с разделителями строк и столбцов
* Синтаксис как у потоков C++
*/
class StreamTable {
public:
std::ostream &os_;
StreamTable(std::ostream &os = std::cout, char delimRow = ' ', char delimCol = ' ') :
borderExtOn_(true),
delimRowOn_(true),
delimRow_(delimRow),
delimColOn_(true),
delimCol_(delimCol),
os_(os),
colIndex_(0),
firstCell_(1) {}
virtual ~StreamTable() {}
virtual std::ostream &os() const {
return os_;
}
//отображать внешние границы?
void MakeBorderExt(bool on) {
borderExtOn_ = on;
}
//символ разделителя строк
void SetDelimRow(bool delimOn, char delimRow = ' ') {
delimRowOn_ = delimOn;
if (delimRowOn_)
delimRow_ = delimRow;
else if (!delimColOn_)
MakeBorderExt(false);
}
//символ разделителя столбцов
void SetDelimCol(bool delimOn, char delimCol = ' ') {
delimColOn_ = delimOn;
if (delimColOn_)
delimCol_ = delimCol;
else if (!delimRowOn_)
MakeBorderExt(false);
}
int AddCol(int colWidth, bool visible = true) {
colWidth_.push_back(colWidth);
visible_.push_back(visible);
return colWidth_.back();
}
void SetVisible(int col, bool flg) {
visible_[col - 1] = flg;
}
void SetCols(int colCount, int colWidth = 0) {
Clear();
for (int ic = 0; ic < colCount; ic++) {
AddCol(colWidth);
}
}
virtual void Clear() {
colWidth_.clear();
visible_.clear();
colIndex_ = 0;
firstCell_ = 1;
}
void AddEmptyRow() {
for (int ic = 0; ic < (int)colWidth_.size(); ic++) {
*this << "";
}
}
template <typename T> StreamTable &operator << (const T &obj) {
Push(obj);
return *this;
}
StreamTable &operator << (const std::string &s) {
colWidth_[colIndex_] = std::max(colWidth_[colIndex_], (int)s.size() + 1);
Push(s);
return *this;
}
StreamTable &operator << (const char *s) {
colWidth_[colIndex_] = std::max(colWidth_[colIndex_], (int)strlen(s) + 1);
Push(s);
return *this;
}
protected:
int colIndex_;
private:
bool borderExtOn_;
bool delimRowOn_;
char delimRow_;
bool delimColOn_;
char delimCol_;
std::vector<int> colWidth_;
bool firstCell_;
std::vector<int> visible_;
template <typename T>
void Push(const T &obj) {
if (firstCell_) {
if (borderExtOn_)
MakeRowBorder();
firstCell_ = 0;
}
if (visible_[colIndex_]) {
DelimCol();
os_.width(colWidth_[colIndex_]);
os_.fill(' ');
os_ << /*std::setiosflags(std::ios::left) << */obj;
}
if (++colIndex_ == (int)colWidth_.size()) {
DelimCol();
if (delimRowOn_)
MakeRowBorder();
else
os_ << CRLF;
colIndex_ = 0;
}
}
void MakeRowBorder() {
os_ << CRLF;
DelimCol();
int ic;
for (ic = 0; ic < (int)colWidth_.size(); ic++) {
if (visible_[ic]) {
os_.width(colWidth_[ic] + 1);
os_.fill(delimRow_);
DelimCol();
}
}
os_ << CRLF;
}
void DelimCol() {
if (delimColOn_ && (borderExtOn_ || colIndex_))
os_ << delimCol_;
else
os_ << ' ';
}
//запрет на копирование
StreamTable &operator = (const StreamTable &);
};
#endif // __STREAM_TABLE_H
Класс StreamTable позволяет построчно выводить табличные данные в формате потоков С++. Принимает в конструкторе ссылку на std::ostream, так что помимо std::cout (по-умолчанию) можно осуществлять запись в файл, передав std::ofstream &. Полезен при формировании лог-файла с результатами расчета.
#include <sstream>
#include "StreamTable.h"
void TestStreamTable1()
{
StreamTable st(std::cout);
st.AddCol(5);
st.AddCol(15);
st.AddCol(10);
st.AddCol(10);
//разкомментировать, если столбцы имеют одинаковую толщину
//st.Clear();
//st.SetCols(4, 10);
//st.SetVisible(1, false);//столбцы можно скрывать
st.MakeBorderExt(true);
st.SetDelimRow(true, '-');//st.SetDelimRow(false);//без символов-разделителей строк
st.SetDelimCol(true, '|');//st.SetDelimCol(false);//без символов-разделителей строк
//заголовок и значения выводятся одинаково
st << "#" << "Property" << "Value" << "Unit";
enum {nr = 10};
for (int i = 0; i < nr; i++) {
st << i + 1 << "Prop" << i << "Unit";
}
}
Для возможности использования StreamTable для выгрузки в MS Excel был реализован специальный поток excelstream и вспомогательный класс для работы с MS Excel:
Файл MSExcel.h
#pragma once
#include <sstream>
#import "C:Program Files (x86)Common FilesMicrosoft SharedOffice12MSO.DLL"
rename("RGB","_RGB")
rename("DocumentProperties", "_DocumentProperties")
rename("SearchPath","_SearchPath")
#import "C:Program Files (x86)Common FilesMicrosoft SharedVBAVBA6VBE6EXT.OLB"
#import "C:Program Files (x86)Microsoft OfficeOffice12EXCEL.EXE"
rename("DialogBox","_DialogBox")
rename("RGB","_RGB")
rename("CopyFile", "_CopyFile")
rename("ReplaceText", "_ReplaceText")
no_auto_exclude
#define THROW(msg) throw std::exception(msg);
/**
*
*/
class ExcelLoader {
public:
virtual ~ExcelLoader() {
Close();
}
Excel::_ApplicationPtr excel_;
Excel::_WorksheetPtr sheet_;
Excel::RangePtr range_;
//отвязываение от Excel, чтобы решение о закрытии и сохранении принял пользователь
void Detach() {
if (!excel_)
return;
range_.Detach();
range_ = 0;
sheet_.Detach();
sheet_ = 0;
excel_.Detach();
excel_ = 0;
::CoUninitialize();
}
//закрывает без сохранения ранее открытую книгу через LoadExcel
void Close() {
if (!excel_)
return;
try {
excel_->DisplayAlerts[0] = false;
excel_->Quit();
excel_->DisplayAlerts[0] = true;
Detach();
//todo: с MS Excel 2010 обнаружилось, что CoUninitialize не всегда закрывает процесс
std::system("taskkill /F /IM Excel.exe");
//ожидаем закрытия процесса EXCEL
while (FindWindow("XLMAIN", NULL)) {};
} catch (_com_error &er) {
THROW(er.ErrorMessage());
}
}
//сохраняет ранее открытую книгу через LoadExcel
void Save() {
if (!excel_)
return;
excel_->DisplayAlerts[0] = false;
try {
excel_->Save();
} catch (_com_error &er) {
THROW(er.ErrorMessage());
}
excel_->DisplayAlerts[0] = true;
}
//инстанцирует Excel и открывает лист с индексом ws_index (>= 1) в книге fname
void LoadExcel(const std::string &fname, int ws_index) {
if (FAILED(::CoInitialize(NULL)))
THROW("CoInitialize failure");
if (FAILED(excel_.CreateInstance("Excel.Application"))) {
std::stringstream ss;
ss << "CreateInstance failed: " << GetLastError();
std::string msg = ss.str();
THROW(msg.c_str());
}
excel_->Visible[0] = TRUE;
Excel::_WorkbookPtr book = excel_->Workbooks->Open(fname.c_str());
if (!book)
THROW(std::string("Can't open ").append(fname).append(": Workbooks->Open method failed").c_str());
sheet_ = excel_->ActiveSheet;
if (ws_index < 1 || excel_->Sheets->Count < ws_index)
THROW("ws_index_ must be in [1, Sheets.Count]");
sheet_ = excel_->Sheets->Item[ws_index];
if (!sheet_)
THROW("Failed to get a pointer to the active sheet");
range_ = sheet_->Cells;
if (!range_)
THROW("Failed to get a pointer to the cells on the active sheet");
}
};
Файл ExcelStream.h
#pragma once
#include <sstream>
#include "MSExcel.h"
typedef char CharT;
typedef std::char_traits<CharT> TraitsT;
class excel_stringbuf : public std::basic_stringbuf<CharT, TraitsT> {
public:
static const char colDelim = 't';
static const char rowDelim = 'n';
virtual ~excel_stringbuf() {
sync();
}
void SetRange(Excel::RangePtr pRange, int irow_offset, int icol_offset) {
rng_ = pRange;
//запись будем производить из верхнего левого узла листа
irow_ = irow_offset;
icol_offset_ = icol_offset;
icol_ = icol_offset;
}
Excel::RangePtr GetRange() const {
return rng_;
}
int &CurRow() {
return irow_;
}
int &CurCol() {
return icol_;
}
int sync() {
output_string(str().c_str());
str(std::basic_string<CharT>());//очистка текстового буфера
return 0;
}
protected:
Excel::RangePtr rng_;
int irow_;
int icol_offset_;
int icol_;
//в отличие от библиотечной isspace не учитывает пробел в качестве разделителя
bool IsSpace(char c) const {
return (c == colDelim) || (c == rowDelim) || (c == 'r');
}
/**
* Записывает строку s в файл Excel.
* При этом признаком перехода к следующему столбцу является символ 't',
* а к следующей строке - 'n'.
* Пример: строка вида "1t2n3t4 5" будет записана в виде
* (1,1) = 1
* (1,2) = 2
* (2,1) = 3
* (2,2) = 4 5,
* где (i,j) - координаты ячейки
* todo: добавить обработку переполнения количества строк/столбцов
*/
void output_string(const std::string &s);
};
void
excel_stringbuf::output_string(const std::string &s)
{
//плавающий указатель на символы строки, отличные от пробельных (n t)
std::string::const_iterator be = s.begin();
std::string::const_iterator en = s.end();
std::string::const_iterator it = be;
while (it != en) {
bool dump = false;
bool isTab = false;
bool isEnd = false;
//если встретился символ перевода столбца или строки определяем,
//необходимо ли произвести запись в текущую ячейку (iRow, iCol)
if (*it == colDelim) {
isTab = true;
dump = !IsSpace(*be);
} else if (*it == rowDelim) {
isEnd = true;
dump = !IsSpace(*be);
} else {
//как только встретился не разделительный символ, выставить на него be
if (IsSpace(*be))
be = it;
if (it + 1 == en) {
//прочли последний символ и он не пробельный
dump = true;
//при записи в ячейку предполагается, что
//it указывает на разделительный символ
it = en;
}
}
if (dump) {
//записать в текущую ячейку часть строки из промежутка [be, it)
const std::string &item = s.substr(be - s.begin(), it - be);
rng_->Item[irow_][icol_] = _variant_t(item.c_str());
}
//обновляем координаты положения в файле следующей строки
if (isTab) {
icol_++;
be = it;
} else if (isEnd) {
irow_++;
icol_ = icol_offset_;
be = it;
}
if (it == en) {
//достигли конца строки
break;
} else
it++;
}
}
/**
* Класс для записи в Excel с синтаксисом как у STL потоков
*/
class excelstream : public std::basic_ostream<CharT, TraitsT> {
public:
excelstream(Excel::RangePtr &rng, int irow_offset = 1, int icol_offset = 1)
: std::basic_ostream<CharT, TraitsT>(&buf_) {
buf_.SetRange(rng, irow_offset, icol_offset);
}
virtual ~excelstream() {
flush();
}
private:
excel_stringbuf buf_;
};
Внимание, поскольку в MSExcel.h используется #import, то для компиляции необходимо прописать актуальные пути к MSO.DLL, VBE6EXT.OLB, EXCEL.EXE в соответствии с установленной версией MS Office.
#include <sstream>
#include "StreamTable.h"
#include "ExcelStream.h"
void TestStreamTable2()
{
//открываем
ExcelLoader xls;
xls.LoadExcel("C:/log.xlsx", 1);
excelstream os(xls.range_, 1, 1);//координаты левого верхнего угла
StreamTable st(os);
st.SetCols(4);//todo: задание ширины Excel-ячеек пока не реализовано
st.MakeBorderExt(false);//обязательно, иначе будут лишние пустые строки
st.SetDelimRow(false);//обязательно, иначе будут лишние пустые строки
st.SetDelimCol(true, excel_stringbuf::colDelim);//обязательно, т.к. excel_stringbuf разбивает строку по столбцам с учетом colDelim
//заголовок таблицы
st << "#" << "Property" << "Value" << "Unit";
enum { nr = 10 };
for (int i = 0; i < nr; i++) {
st << i + 1 << "Prop" << i << "Unit";
}
os.flush();
xls.Detach();//после Detach вызов os или st некорректно
}
Поток excelstream в составе StreamTable работает таким образом, что его буфер парсит строку выгрузки, в которой символ ‘t’ интерпретируется как переход к столбцу вправо, а ‘n’ — как символ перехода на следующую строку. Пример ниже аналогичен TestStreamTable2 по результату и показывает принцип формирования такой строки.
#include <sstream>
#include "ExcelStream.h"
void TestStreamTable3()
{
ExcelLoader xls;
xls.LoadExcel("C:/log.xlsx", 1);
excelstream os(xls.range_, 1, 1);
os << "#tPropertytValuetUnitn";
std::stringstream ss;
enum { nr = 10 };
for (int i = 0; i < nr; i++) {
os << i + 1 << "tPropt" << i << "tUnitn";
}
os.flush();
xls.Detach();//после Detach вызов os или st некорректно
}
Файл со всеми примерами main.cpp
#include <sstream>
#include "StreamTable.h"
#include "ExcelStream.h"
void TestStreamTable1()
{
StreamTable st(std::cout);
st.AddCol(5);
st.AddCol(15);
st.AddCol(10);
st.AddCol(10);
//разкомментировать, если столбцы имеют одинаковую толщину
//st.Clear();
//st.SetCols(4, 10);
//st.SetVisible(1, false);//столбцы можно скрывать
st.MakeBorderExt(true);
st.SetDelimRow(true, '-');//st.SetDelimRow(false);//без символов-разделителей строк
st.SetDelimCol(false, '|');//st.SetDelimCol(false);//без символов-разделителей строк
//заголовок и значения выводятся одинаково
st << "#" << "Property" << "Value" << "Unit";
enum {nr = 10};
for (int i = 0; i < nr; i++) {
st << i + 1 << "Prop" << i << "Unit";
}
}
void TestStreamTable2()
{
//открываем
ExcelLoader xls;
xls.LoadExcel("C:/log.xlsx", 1);
excelstream os(xls.range_, 1, 1);//координаты левого верхнего угла
StreamTable st(os);
st.SetCols(4);//todo: задание ширины Excel-ячеек пока не реализовано
st.MakeBorderExt(false);//обязательно, иначе будут лишние пустые строки
st.SetDelimRow(false);//обязательно, иначе будут лишние пустые строки
st.SetDelimCol(true, excel_stringbuf::colDelim);//обязательно, т.к. excel_stringbuf разбивает строку по столбцам с учетом colDelim
//заголовок таблицы
st << "#" << "Property" << "Value" << "Unit";
enum { nr = 10 };
for (int i = 0; i < nr; i++) {
st << i + 1 << "Prop" << i << "Unit";
}
os.flush();
xls.Detach();//после Detach вызов os или st некорректно
}
void TestStreamTable3()
{
ExcelLoader xls;
xls.LoadExcel("C:/log.xlsx", 1);
excelstream os(xls.range_, 1, 1);
os << "#tPropertytValuetUnitn";
std::stringstream ss;
enum { nr = 10 };
for (int i = 0; i < nr; i++) {
os << i + 1 << "tPropt" << i << "tUnitn";
}
os.flush();
xls.Detach();//после Detach вызов os или st некорректно
}
void main()
{
try {
TestStreamTable1();
TestStreamTable2();
TestStreamTable3();
} catch(std::exception &ex) {
std::cout << ex.what();
}
}
В заключение отмечу, что в отношении excelstream не проверялась его работа как полиморфного указателя на std::ostream, поэтому в этой части класс следует дорабатывать.
rafael999 0 / 0 / 0 Регистрация: 14.09.2013 Сообщений: 148 |
||||
1 |
||||
25.11.2013, 21:01. Показов 12673. Ответов 7 Метки нет (Все метки)
Помогите пожалуйста, программа в консоль и блокнот выводит отлично, а вот с екселем беда!!!
Миниатюры
0 |
Programming Эксперт 94731 / 64177 / 26122 Регистрация: 12.04.2006 Сообщений: 116,782 |
25.11.2013, 21:01 |
Ответы с готовыми решениями: Вывод данных в Excel Вывод данных в Excel Вывод данных в Excel Вывод данных из БД в Excel 7 |
419 / 418 / 167 Регистрация: 28.11.2010 Сообщений: 1,183 |
|
25.11.2013, 21:30 |
2 |
для экселя поменяй точки на запятые
1 |
0 / 0 / 0 Регистрация: 14.09.2013 Сообщений: 148 |
|
25.11.2013, 21:33 [ТС] |
3 |
где? укажите пожалуйста
0 |
37 / 31 / 4 Регистрация: 21.10.2013 Сообщений: 197 |
|
25.11.2013, 21:35 |
4 |
rafael999, в том, что выводишь.
1 |
419 / 418 / 167 Регистрация: 28.11.2010 Сообщений: 1,183 |
|
25.11.2013, 21:37 |
5 |
вместо точек, которые делят целую и дробную часть, или включите правильные настройки локали. По умолчанию для русской локали — запятая отделяет целую от дробной части
1 |
18 / 18 / 3 Регистрация: 16.09.2013 Сообщений: 126 |
|
25.11.2013, 21:38 |
6 |
где? укажите пожалуйста Предположительно в переменных. Видимо придется вам переводить в строку и обратно.(Предположение!)
1 |
0 / 0 / 0 Регистрация: 14.09.2013 Сообщений: 148 |
|
25.11.2013, 21:43 [ТС] |
7 |
как указать правильный настройки?
0 |
18 / 18 / 3 Регистрация: 16.09.2013 Сообщений: 126 |
|
25.11.2013, 21:53 |
8 |
как указать правильный настройки? предположительно что то из этого(но не уверен)
0 |
IT_Exp Эксперт 87844 / 49110 / 22898 Регистрация: 17.06.2006 Сообщений: 92,604 |
25.11.2013, 21:53 |
Помогаю со студенческими работами здесь Вывод данных в excel Вывод данных в Excel вывод данных в excel //Процедура вывода данных в excel Вывод данных в excel Программа должна выводить данные после их… Искать еще темы с ответами Или воспользуйтесь поиском по форуму: 8 |
In this post, you will find a description of the C# Generic List data output extension methods that have many features and are easy to use.
- Download source — 6.7 KB
Introduction
The Generic List is my mostly used collection data structure. I very often need to view all or parts of the data from the Generic List with particular type in a console, debugging, or Excel Worksheet window, or sometimes send the data to a CSV file. There are so many tools and code snippets regarding the data output from a List to a string or CSV/Excel file. However, I couldn’t find one that meets my needs. Hence I wrote my own methods based on the following requirements.
- Easy to use as a form of extension methods for the
List<T>
- Options for including or excluding data fields (or properties of the object type)
- Formatted
string
for a list of object item if the output is a singlestring
- Ability to save the data from
List<T>
to a CSV file - Directly opening a Microsoft Excel Worksheet window with the data in the
List<T>
Extension Method Syntax
Output to string
(overloaded extension method):
public string IList.ToString<T>([string include = ""], [string exclude = ""])
Output to CSV file:
public void IList.ToCSV<T>([string path = ""], [string include = ""], [string exclude = ""])
Output to Excel (without using Interop
library):
public void IList.ToExcelNoInterop<T> ([string path = ""], [string include = ""], [string exclude = ""])
Output to Excel (no file creation but need to use Interop
library):
public void IList.ToExcel<T>([string include = ""], [string exclude = ""])
All arguments of the methods are optional. It is recommended using named argument form, i.e., «parameter_name: parameter_value
» regardless of the parameter sequence. For example, you can call to create a CSV file from the data in a list (DataSource.Products
) having the Product
type like this:
DataSource.Products.ToCSV<Product> (exclude: "ProductId,OutOfStock", path:@"D:TestProducts.csv");
Extension Method Details
The GenericListOutput.cs file in the downloaded source contains the code shown below. All necessary comments are attached or code lines are self-explainable. You can add this class into any C# project using the .NET Framework 4.0/Visual C# 2010 and above. The List<T>
extension methods should then be ready to use. You may need to add some assembly references to your project if any required reference is missing.
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Excel = Microsoft.Office.Interop.Excel; public static class GenericListOutput { public static string ToString<T> (this IList<T> list, string include = "", string exclude = "") { string propStr = string.Empty; StringBuilder sb = new StringBuilder(); PropertyInfo[] props = typeof(T).GetProperties(); List<PropertyInfo> propList = GetSelectedProperties(props, include, exclude); string typeName = GetSimpleTypeName(list); sb.AppendLine(string.Format("{0} List - Total Count: {1}", typeName, list.Count.ToString())); foreach (var item in list) { sb.AppendLine(""); foreach (var prop in propList) { propStr = prop.Name + ": " + prop.GetValue(item, null); sb.AppendLine(propStr); } } return sb.ToString(); } public static void ToCSV<T>(this IList<T> list, string path = "", string include = "", string exclude = "") { CreateCsvFile(list, path, include, exclude); } public static void ToExcelNoInterop<T>(this IList<T> list, string path = "", string include = "", string exclude = "") { if (path == "") path = Path.GetTempPath() + @"ListDataOutput.csv"; var rtnPath = CreateCsvFile(list, path, include, exclude); Process proc = new Process(); proc.StartInfo = new ProcessStartInfo("excel.exe", """ + rtnPath + """); proc.Start(); } private static string CreateCsvFile<T>(IList<T> list, string path, string include, string exclude) { StringBuilder sb = new StringBuilder(); List<string> propNames; List<string> propValues; bool isNameDone = false; PropertyInfo[] props = typeof(T).GetProperties(); List<PropertyInfo> propList = GetSelectedProperties(props, include, exclude); string typeName = GetSimpleTypeName(list); sb.AppendLine(string.Format("{0} List - Total Count: {1}", typeName, list.Count.ToString())); foreach (var item in list) { sb.AppendLine(""); propNames = new List<string>(); propValues = new List<string>(); foreach (var prop in propList) { if (!isNameDone) propNames.Add(prop.Name); var val = prop.PropertyType == typeof(string) ? ""{0}"" : "{0}"; propValues.Add(string.Format(val, prop.GetValue(item, null))); } string line = string.Empty; if (!isNameDone) { line = string.Join(",", propNames); sb.AppendLine(line); isNameDone = true; } line = string.Join(",", propValues); sb.Append(line); } if (!string.IsNullOrEmpty(sb.ToString()) && path != "") { File.WriteAllText(path, sb.ToString()); } return path; } public static void ToExcel<T> (this IList<T> list, string include = "", string exclude = "") { PropertyInfo[] props = typeof(T).GetProperties(); List<PropertyInfo> propList = GetSelectedProperties(props, include, exclude); string typeName = GetSimpleTypeName(list); object[,] listArray = new object[list.Count + 1, propList.Count]; int colIdx = 0; foreach (var prop in propList) { listArray[0, colIdx] = prop.Name; colIdx++; } int rowIdx = 1; foreach (var item in list) { colIdx = 0; foreach (var prop in propList) { listArray[rowIdx, colIdx] = prop.GetValue(item, null); colIdx++; } rowIdx++; } object oOpt = System.Reflection.Missing.Value; Excel.Application oXL = new Excel.Application(); Excel.Workbooks oWBs = oXL.Workbooks; Excel.Workbook oWB = oWBs.Add(Excel.XlWBATemplate.xlWBATWorksheet); Excel.Worksheet oSheet = (Excel.Worksheet)oWB.ActiveSheet; oSheet.Name = typeName; Excel.Range oRng = oSheet.get_Range("A1", oOpt).get_Resize(list.Count+1, propList.Count); oRng.set_Value(oOpt, listArray); oXL.Visible = true; } private static List<PropertyInfo> GetSelectedProperties (PropertyInfo[] props, string include, string exclude) { List<PropertyInfo> propList = new List<PropertyInfo>(); if (include != "") { var includeProps = include.ToLower().Split(',').ToList(); foreach (var item in props) { var propName = includeProps.Where (a => a == item.Name.ToLower()).FirstOrDefault(); if (!string.IsNullOrEmpty(propName)) propList.Add(item); } } else if (exclude != "") { var excludeProps = exclude.ToLower().Split(','); foreach (var item in props) { var propName = excludeProps.Where (a => a == item.Name.ToLower()).FirstOrDefault(); if (string.IsNullOrEmpty(propName)) propList.Add(item); } } else { propList.AddRange(props.ToList()); } return propList; } private static string GetSimpleTypeName<T>(IList<T> list) { string typeName = list.GetType().ToString(); int pos = typeName.IndexOf("[") + 1; typeName = typeName.Substring(pos, typeName.LastIndexOf("]") - pos); typeName = typeName.Substring(typeName.LastIndexOf(".") + 1); return typeName; } }
Property Selection for Data Output
Providing options of selecting output data properties (a.k.a., fields) is a powerful feature for viewing the desired data held in the Generic List. We usually use LINQ to select object item in a List
collection (equivalent to rows in a table) but use reflection to select properties (equivalent to columns in a table). Sometimes, we want to exclude some properties, especially for auto added non-user data properties in particular type of projects. Other times, we may just need part, but not all, of the properties. With these extension methods, we can specify the property names in a comma delimited string as the optional include
or exclude
argument based on the needs. These arguments are case-insensitive for easy use although the property names in the object are case-sensitive.
Also note that the include
argument, if having a non-empty string
value, will always take the precedence in the code processing logic.
As mentioned in previous section, the named argument form is recommended for calling these extension methods. Named arguments free you from the need to remember or to fit the order of parameters in the parameter lists of called methods, especially for the optional arguments. You can see the examples in the below section.
Would Not Like Interop?
The IList<T>.ToExcel()
method calls the Microsoft.Office.Interop.Excel
library, converts the List
data to the Excel Worksheet format, and then opens the Excel window without any file created first. Some developers may not like to add the Interop reference to projects. Then the alternative is to open the Excel from the created CSV file using the IList<T>.ToExcelNoInterop()
method. There are two options on dealing with the CSV file creation when using this option.
- Calling the method without taking care of the CSV file. The method defaults the path to the current user’s temporary directory with the file name ListDataOutput.csv. You will take no action on the CSV file as if it was not there. Although this file will not automatically be deleted, only one file with this name is kept in the temporary directory. This temporary file will be overwritten each time when you call the same method. You can, of course, manually save the CSV file or the Worksheet file as whatever file name or available type to any other location from the opened Excel window.
- Calling the method with file path you specify. In this case, the CSV file is saved to the path specified and the Excel window will automatically be opened with the data from the CSV file.
Double Quote Wrappers
The code in two CSV related methods handles some small tricks by using double quote wrappers to prevent those methods or data output from exceptions that could break the method executions or data structures.
- When a file path contains space in folder or file names, the methods in the .NET Framework
System.IO.Path
class such asGetTempPath()
handles them well. It’s not so lucky if we inject such a file pathstring
into theProcessStartInfo
constructor. When starting the process of Excel application, we need to send the pathstring
wrapped by double quotes as shown in this line of code:proc.StartInfo = new ProcessStartInfo("excel.exe", """ + path + """);
- Any comma in a data value will break the intact structure of a CSV file if the value is not wrapped by double quotes. But we may only need the double quotes for values of
string
type. Since we already get the property into using the reflection, we can easily add the code for the issue.var val = prop.PropertyType == typeof(string) ? ""{0}"" : "{0}"; propValues.Add(string.Format(val, prop.GetValue(item, null)));
Examples of Calling Extension Methods
The downloaded source includes the DataSource
class in which the Products
property points to the List<Product>
collection that is populated with the Product
data. After you build the sample application, you can run it to see all four kinds of output results or just test the methods one by one.
- Output to a
string
for displaying in the Console window:string result = DataSource.Products.ToString<Product> (include:"ProductName,CategoryId,UnitPrice"); Console.Write(result);
The screenshot below shows the formatted
string
for theList
data. - Output to a CSV file:
DataSource.Products.ToCSV<Product> (exclude: "ProductId,OutOfStock", path: @"D:TestProducts.csv");
This saves the CSV file in the path specified.
- Opening an Excel Worksheet window from a CSV file created without referencing the
Interop
:DataSource.Products.ToExcelNoInterop<Product>(exclude: "ProductId,OutOfStock");
This saves the ListDataOutput.csv file in the user temporary directory by default and then automatically opens the Excel Worksheet window.
- Opening an Excel Worksheet window referencing the
Interop
library:DataSource.Products.ToExcel<Product>(exclude: "ProductId");
No file is created in this case. Sheet1 is displayed in the window’s title. When exiting the Excel window, you will then be prompted to save the file.
Summary
The C# Generic List data output extension methods described here have many features and are easy to use. I hope that these methods are helpful to developers who need them for their daily work.
History
- 19th Nov 2013: Initial version
Shenwei is a software developer and architect, and has been working on business applications using Microsoft and Oracle technologies since 1996. He obtained Microsoft Certified Systems Engineer (MCSE) in 1998 and Microsoft Certified Solution Developer (MCSD) in 1999. He has experience in ASP.NET, C#, Visual Basic, Windows and Web Services, Silverlight, WPF, JavaScript/AJAX, HTML, SQL Server, and Oracle.
Автор: Кулюкин Олег
Дата: 27 декабря 2001 года
Рано или поздно практически каждый программист сталкивается с необходимостью организовать экспорт данных в MS Office. При этом каждое «поколение» программистов натыкается на одни и те же вилы.
Вот три часто встречающихся вопроса.
- Как определить установлен ли Excel
- Как определить запущен ли Excel
- Как вывести данные в Excel
Большую помощь в понимании этих и других вопросов приносит чтение исходных текстов функций модуля ComObj.
Во всех случаях следует подключить модули ComObj и ActiveX
1. Как определить установлен ли Excel
Функция возвращает True если найден OLE-объект
Пример использования
if not IsOLEObjectInstalled('Excel.Application') then ShowMessage('Класс не зарегистрирован') else ShowMessage('Класс найден'); function IsOLEObjectInstalled(Name: String): boolean; var ClassID: TCLSID; Rez : HRESULT; begin // Ищем CLSID OLE-объекта Rez := CLSIDFromProgID(PWideChar(WideString(Name)), ClassID); if Rez = S_OK then // Объект найден Result := true else Result := false; end;
Если нужна более подробная информация об объекте, можно почитать хелп по функции API CLSIDFromProgID.
2. Как определить запущен ли Excel
Данный пример ищет активный экземпляр Excel и делает его видимым
var ExcelApp : Variant; begin try // Ищем запущеный экземплят Excel, если он не найден, вызывается исключение ExcelApp := GetActiveOleObject('Excel.Application'); // Делаем его видимым ExcelApp.Visible := true; except end;
3. Как вывести данные в Excel
Можно выводить данные последовательно в каждую ячейку, но это очинь сильно замедляет работу. Лучше сформировать вариантный массив, и выполнить присвоение области (Range) этого массива.
var ExcelApp, Workbook, Range, Cell1, Cell2, ArrayData : Variant; TemplateFile : String; BeginCol, BeginRow, i, j : integer; RowCount, ColCount : integer; begin // Координаты левого верхнего угла области, в которую будем выводить данные BeginCol := 1; BeginRow := 5; // Размеры выводимого массива данных RowCount := 100; ColCount := 50; // Создание Excel ExcelApp := CreateOleObject('Excel.Application'); // Отключаем реакцию Excel на события, чтобы ускорить вывод информации ExcelApp.Application.EnableEvents := false; // Создаем Книгу (Workbook) // Если заполняем шаблон, то Workbook := ExcelApp.WorkBooks.Add('C:MyTemplate.xls'); Workbook := ExcelApp.WorkBooks.Add; // Создаем Вариантный Массив, который заполним выходными данными ArrayData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant); // Заполняем массив for I := 1 to RowCount do for J := 1 to ColCount do ArrayData[I, J] := J * 10 + I; // Левая верхняя ячейка области, в которую будем выводить данные Cell1 := WorkBook.WorkSheets[1].Cells[BeginRow, BeginCol]; // Правая нижняя ячейка области, в которую будем выводить данные Cell2 := WorkBook.WorkSheets[1].Cells[BeginRow + RowCount - 1, BeginCol + ColCount - 1]; // Область, в которую будем выводить данные Range := WorkBook.WorkSheets[1].Range[Cell1, Cell2]; // А вот и сам вывод данных // Намного быстрее поячеечного присвоения Range.Value := ArrayData; // Делаем Excel видимым ExcelApp.Visible := true;
Все привет, в этой статье опишу исчерпывающие примеры работы с excel на языке C#.
Для начала работы нам необходимо подключить библиотеку COM как на рисунке ниже:
Для этого добавляем ссылку в проект, надеюсь вы знаете как это делается) Выбираем пункт COM ищем библиотеку Microsoft Excel 16.0 Object Library ставим галочку и жмем Ок.
Далее нам не обходимо для сокращения записи и удобства создать алиас.
using Excel = Microsoft.Office.Interop.Excel; |
Теперь нам нужно объявить объект Excel задать параметры и приступать к работе.
//Объявляем приложение Excel.Application app = new Excel.Application { //Отобразить Excel Visible = true, //Количество листов в рабочей книге SheetsInNewWorkbook = 2 }; //Добавить рабочую книгу Excel.Workbook workBook = app.Workbooks.Add(Type.Missing); //Отключить отображение окон с сообщениями app.DisplayAlerts = false; //Получаем первый лист документа (счет начинается с 1) Excel.Worksheet sheet = (Excel.Worksheet)app.Worksheets.get_Item(1); //Название листа (вкладки снизу) sheet.Name = «Имя должно быть не больше 32сим»; |
Пример заполнения ячейки:
//Пример заполнения ячеек №1 for (int i = 1; i <= 9; i++) { for (int j = 1; j < 9; j++) sheet.Cells[i, j] = String.Format(«nookery {0} {1}», i, j); } //Пример №2 sheet.Range[«A1»].Value = «Пример №2»; //Пример №3 sheet.get_Range(«A2»).Value2 = «Пример №3»; |
Захват диапазона ячеек:
//Захватываем диапазон ячеек Вариант №1 Excel.Range r1 = sheet.Cells[1, 1]; Excel.Range r2 = sheet.Cells[9, 9]; Excel.Range range1 = sheet.get_Range(r1, r2); //Захватываем диапазон ячеек Вариант №2 Excel.Range range2 = sheet.get_Range(«A1»,«H9» ); |
Оформление, шрифт, размер, цвет, толщина.
//Шрифт для диапазона range.Cells.Font.Name = «Tahoma»; range2.Cells.Font.Name = «Times New Roman»; //Размер шрифта для диапазона range.Cells.Font.Size = 10; //Жирный текст range.Font.Bold = true; //Цвет текста range.Font.Color = ColorTranslator.ToOle(Color.Blue); |
Объединение ячеек в одну
//Объединение ячеек с F2 по K2 Excel.Range range3 = sheet.get_Range(«F2», «K2»); range3.Merge(Type.Missing); |
Изменяем размеры ячеек по ширине и высоте
//увеличиваем размер по ширине диапазон ячеек Excel.Range range2 = sheet.get_Range(«D1», «S1»); range2.EntireColumn.ColumnWidth = 10; //увеличиваем размер по высоте диапазон ячеек Excel.Range rowHeight = sheet.get_Range(«A4», «S4»); rowHeight.EntireRow.RowHeight = 50;range.EntireColumn.AutoFit();range.EntireColumn.AutoFit(); //авторазмер |
Создаем обводку диапазона ячеек
Excel.Range r1 = sheet.Cells[countRow, 2]; Excel.Range r2 = sheet.Cells[countRow, 19]; Excel.Range rangeColor = sheet.get_Range(r1, r2); rangeColor.Borders.Color = ColorTranslator.ToOle(Color.Black); |
Производим выравнивания содержимого диапазона ячеек.
Excel.Range r = sheet.get_Range(«A1», «S40»); //Оформления r.Font.Name = «Calibri»; r.Cells.Font.Size = 10; r.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; r.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter; |
Примеры вычисления формул, все вставки формул были скопированы из самой Excel без изменений. Позиция ячейки взята из счетчика переменно и подставлен к букве ячейки
sheet.Cells[countRow, countColumn] = $«=G{countRow}-F{countRow}»; sheet.Cells[countRow, countColumn].FormulaLocal = $«=ЕСЛИ((H{countRow}*O{countRow})+(I{countRow}*P{countRow})/100<=0;J{countRow}*O{countRow}/100;((H{countRow}*O{countRow})+(I{countRow}*P{countRow}))/100)»; sheet.Cells[countRow, countColumn] = $«=K{countRow}+N{countRow}-R{countRow}»; sheet.Cells[33, 22].FormulaLocal = «=СУММ(V3:V32)»; |
Добавляем разрыв страницы.
//Ячейка, с которой будет разрыв Excel.Range razr = sheet.Cells[n, m] as Excel.Range; //Добавить горизонтальный разрыв (sheet — текущий лист) sheet.HPageBreaks.Add(razr); //VPageBreaks — Добавить вертикальный разрыв |
Как открыть фаил Excel
app.Workbooks.Open(@»C:UsersUserDocumentsExcel.xlsx», Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); |
Сохраняем документ Excel
app.Application.ActiveWorkbook.SaveAs(«MyFile.xlsx», Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); |
Завершение работы с объектом Excel.Application
app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(app); |
Пример как выбрать фаил и загрузив его и узнать количество заполненных строк и колонок в одном конкретном листе по имени.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//поиск файла Excel OpenFileDialog ofd = new OpenFileDialog(); ofd.Multiselect =false; ofd.DefaultExt = «*.xls;*.xlsx»; ofd.Filter = «Microsoft Excel (*.xls*)|*.xls*»; ofd.Title = «Выберите документ Excel»; if (ofd.ShowDialog() != DialogResult.OK) { MessageBox.Show(«Вы не выбрали файл для открытия», «Внимание», MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string xlFileName = ofd.FileName; //имя нашего Excel файла //рабоата с Excel Excel.Range Rng; Excel.Workbook xlWB; Excel.Worksheet xlSht; int iLastRow, iLastCol; Excel.Application xlApp = new Excel.Application(); //создаём приложение Excel xlWB = xlApp.Workbooks.Open(xlFileName); //открываем наш файл xlSht = xlWB.Worksheets[«Лист1»]; //или так xlSht = xlWB.ActiveSheet //активный лист iLastRow = xlSht.Cells[xlSht.Rows.Count, «A»].End[Excel.XlDirection.xlUp].Row; //последняя заполненная строка в столбце А iLastCol = xlSht.Cells[1, xlSht.Columns.Count].End[Excel.XlDirection.xlToLeft].Column; //последний заполненный столбец в 1-й строке |
Получаем список всех загруженных книг «листов» из файла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//поиск файла Excel OpenFileDialog ofd = new OpenFileDialog(); ofd.Multiselect = false; ofd.DefaultExt = «*.xls;*.xlsx»; ofd.Filter = «Microsoft Excel (*.xls*)|*.xls*»; ofd.Title = «Выберите документ Excel»; if (ofd.ShowDialog() != DialogResult.OK) { MessageBox.Show(«Вы не выбрали файл для открытия», «Внимание», MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string xlFileName = ofd.FileName; //имя нашего Excel файла Excel.Workbook xlWB = ex.Workbooks.Open(xlFileName); ///загружаем список всех книг foreach (object item in xlWB.Sheets) { Excel.Worksheet sheet = (Excel.Worksheet)item; } |