Создание собственного элемента на примере таблицы на C# для Windows Form
В данной статье я опишу создания своих элементов для C# Windows Form.
Для примера буду создавать таблицу со всем функционалом DataGridView. Позже перейдем на свои элементы. Создание первого элемента разобьем на несколько уроков. В данном уроке произведем от рисовку таблицы, а также: создание столбцов, строк, ячеек.
Для написания будем использовать .Net FrameWork 4.7.x, среда разработки Visual Studio 2019.
В первую очередь создадим обычный проект Windows Form. Думаю не нужно это показывать. А уже потом создаем проект «Библиотека элементов управления Windows Form»(Назовем его CustomControl).
Далее у нас будет создан файл UserControl.cs. Удаляем его и создаем обычный класс TableCustoms.cs.Наш класс будет наследоваться от класса Control.
Далее в этом же файле создадим еще несколько классов, а именно:Column,Row,Cell. Рассмотрим каждый по отдельности. Начнем с Column:
[Serializable] public class Column < public string Name < get; set; >= "NameColumn";//Наименование Столбца public string Caption < get; set; >= "CaptionColumn";//Текст заголовка public int Width < get; set; >= 100;//Стандартная ширина public Color Back < get; set; >= Color.White;//Цвет фона public Column() < >>
[Serializable] public class Row < public int Heigth < get; set; >= 20;// Высота строки public List Cells < get; set; >= new List();//список ячеек public Row() < >public Row(List cells) < Cells = cells; >>
Класс Cell(Для поддержки копирования добавляем интерфейс ICloneable):
[Serializable] public class Cell : ICloneable < public object Value < get; set; >= null;//значение ячейки public Cell() < >public object Clone() < return MemberwiseClone(); >>
Теперь настроим наш основной класс TableCustoms:
public class TableCustoms : Control < #region Перемененные public ObservableCollectionColumns < get; set; >= new ObservableCollection();//Список столбцов таблицы private ObservableCollection rows = new ObservableCollection();//Список строк private int countRow = 0;//количество строк #endregion #region Свойства public int CountRow // гетер и сетер при увеличении переменной на N раз < get < return countRow; >set < //При увеличении добавляем if (value >countRow) < int iteration = value - countRow; for (int i = 0; i < iteration; i++) < rows.Add(new Row()); >> //при уменьшении удаляем с конца if (value < countRow) < int iteration = countRow - value; for (int i = 0; i < iteration; i++) < rows.Remove(rows[rows.Count - 1]); >> countRow = value; > > //гетер и сетер для списка строк, будет использоваться позже public ObservableCollection Rows < get < return rows; >set < >> public int ColumnHeaderHeigth < get; set; >= 20;//высота шапки таблицы public int RowHeaderWidth < get; set; >= 20;//высота заголовков строк public Color ColumnHeaderBack < get; set; >= SystemColors.Control;//Основной цвет фона заголовков таблицы public Color BorderColor < get; set; >= Color.Black;//Стандартный цвет границ таблицы public bool NumerableRows < get; set; >= false;//Флаг автоматической нумерации #endregion //Метода изменения столбцов, будет использоваться в следующем уроке private void EditColumn() < >//Метод изменения строк private void EditRows() < if (countRow < rows.Count)//Увеличение количества строк < rows[rows.Count - 1].Cells = CreatCells(Columns.Count);//Добавление пустых ячеек в строку countRow++; >if (CountRow > rows.Count)//уменьшение количества строк < countRow--; >> //метод создания N количества ячеек private List CreatCells(int Count) < // return Enumerable.Repeat(new Cell(), Count).ToList(); Listresult = new List(); for (int i = 0; i < Count; i++) < result.Add(new Cell()); >return result; > public TableCustoms() < rows.CollectionChanged += (e, v) =>EditRows();//проверка изменения списка Columns.CollectionChanged += (e, v) => EditColumn();//проверка изменения списка BackColor = SystemColors.AppWorkspace;//Стандартный фон PanelTable panelTable = new PanelTable(this);//Создание основной панели panelTable.Dock = DockStyle.Fill;//Растягиваем основную панель по Control Controls.Add(panelTable);//Добавление панели на Control > >
Для того, чтобы у нас были полосы прокрутки нужно использовать ScrollableControl, поэтому создадим класс PanelTable наследуем ScrollableControl и помещаем его на Control(в следующем уроке объясню почему создаем два разных контрола, а не используем сразу ScrollableControl):
internal class PanelTable : ScrollableControl//Control со ScrolLbar < private TableCustoms BParent;//переменная основного класса, для работы с свойствами public PanelTable(TableCustoms bParent) < HScroll = true;//Отображение ползунка по горизонтали VScroll = true;//Отображение ползунка по вертикали AutoScroll = true;//Автоматическое появление полос прокрутки BParent = bParent; >//переопределение метода protected override void OnPaint(PaintEventArgs e) < Matrix m = new Matrix(); m.Translate(this.AutoScrollPosition.X, this.AutoScrollPosition.Y, MatrixOrder.Append); e.Graphics.Transform = m; Graphics graf = e.Graphics; int maxWidth = 0;//Высота AutoScrollMinSize int maxHeight = 0;//Ширина AutoScrollMinSize //расчитываем ширину foreach (Column item in BParent.Columns) < maxWidth += item.Width; >//расчитываем высоту foreach (Row item in BParent.Rows) < maxHeight += item.Heigth; >AutoScrollMinSize = new Size(maxWidth + 100, maxHeight + 100);//назначаем AutoScrollMinSize относительно этого будут появляться полосы прокрутки graf.Clear(BParent.BackColor); DrawHeaderColumns(graf);//Отрисовка заголовков столбцов таблицы DrawHeaderRows(graf);//Отрисовка заголовков строк таблицы DrawCells(graf);//Отрисовка ячеек base.OnPaint(e); > /// /// Отрисока заголовков столбцов /// /// private void DrawHeaderColumns(Graphics graf) < int x = 2; Rectangle rect; rect = new Rectangle(x, 1, BParent.RowHeaderWidth, BParent.ColumnHeaderHeigth); graf.DrawRectangle(new Pen(BParent.BorderColor), rect); graf.FillRectangle(new SolidBrush(BParent.ColumnHeaderBack), rect); x += BParent.RowHeaderWidth + 1; foreach (Column item in BParent.Columns) < rect = new Rectangle(x, 1, item.Width, BParent.ColumnHeaderHeigth); graf.DrawRectangle(new Pen(BParent.BorderColor), rect); graf.FillRectangle(new SolidBrush(BParent.ColumnHeaderBack), rect); if (item.Caption.Length != 0) < StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; graf.DrawString(item.Caption, new Font("Times", 9), Brushes.Black, rect, sf); >x += item.Width + 1; > > //Отрисовка заголовков строк private void DrawHeaderRows(Graphics graf) < int y = 1; int i = 0; Rectangle rect; y += BParent.RowHeaderWidth + 1; foreach (Row item in BParent.Rows) < rect = new Rectangle(2, y, BParent.RowHeaderWidth, item.Heigth); graf.DrawRectangle(new Pen(BParent.BorderColor), rect); graf.FillRectangle(new SolidBrush(BParent.ColumnHeaderBack), rect); if (BParent.NumerableRows) < StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; graf.DrawString(i.ToString(), new Font("Times", 9), Brushes.Black, rect, sf); >i++; y += item.Heigth + 1; > > //отрисовка ячеек private void DrawCells(Graphics graf) < int x = 2 + BParent.RowHeaderWidth + 1; int y = 2 + BParent.ColumnHeaderHeigth; Rectangle rect; int i = 0; foreach (Row itemRow in BParent.Rows) < foreach (Column itemColumn in BParent.Columns) < rect = new Rectangle(x, y, itemColumn.Width, itemRow.Heigth); graf.DrawRectangle(new Pen(BParent.BorderColor), rect); graf.FillRectangle(new SolidBrush(Color.White), rect); if (itemRow.Cells[i].Value != null) < StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; graf.DrawString(itemRow.Cells[i].Value.ToString(), new Font("Times", 9), Brushes.Black, rect, sf); >x += itemColumn.Width + 1; i++; > i = 0; y += itemRow.Heigth + 1; x = 2 + BParent.RowHeaderWidth + 1; > > >
После этого «Пересобираем проект» элемента и добавляем элемент на форму(в основном проекте):

Теперь проверим некоторые методы нашего элемента:
public partial class Form1 : Form < public Form1() < InitializeComponent(); tableCustoms1.Rows.Add(new Row());//добавление строки tableCustoms1.Rows.Add(new Row()); tableCustoms1.Rows[0].Cells[0].Value = "1";//Изменение значения ячейки tableCustoms1.Rows[1].Cells[1].Value = "2"; tableCustoms1.CountRow++;//увеличение числа строк tableCustoms1.Rows[0].Cells[0].Value = "привет"; >>
Старт работы с Excel на C#
В современном мире разработки приложений нередко встает необходимость работы с Excel документами. Чаще всего это разного рода отчеты, но иногда xls/x файлы используются в качестве хранилища данных. Например, если пользователь должен иметь возможность загрузить данные в приложение или выгрузить, в человеко-читаемом виде, Excel де-факто является стандартом. Относительно дружелюбный интерфейс, прозрачная структура, в купе с его распространенностью. трудно навскидку назвать решение лучше.
Однако, у многих Excel до сих пор ассоциируется с чем-то тяжелым, неповоротливым и сложным. Давайте посмотрим, как мы — обычные C# разработчики, можем легко сформировать простой Excel документ, на примере табличного отчета.
Историческая справка
Времена, когда доминировал проприетарный формат .xls(Excel Binary File Format) давно прошли и сейчас мы имеем только .xlsx(Excel Workbook), в рамках Office Open XML. Последний представляет собой обычный .zip архив с XML файлами. Не будем углубляться в его структуру, я искренне надеюсь что вам это никогда не понадобится.
На github, и не только, можно найти ряд библиотек, бесплатных и не только. Пожалуй самой популярной является EPPlus. До определенной степени, она довольно хорошо отражает концепцию Excel, именно по этому я всегда использую EPPlus. Версия 4 полностью бесплатна, начиная с 5‐й версии вам потребуется приобрести лицензию для коммерческого использования.
Задача
Итак, предположим, продукт-мэнеджеру ударила в голову идея того, что возможность выгружать некий отчет в формате Excel увеличит кол-во пользователей на 100500%. Проджет-менеджер решает выкатить эту киллер-фичу как хотфикс прямо сегодня — ведь работы всего на пару часов.
Сам по себе, отчет содержит краткое описание компании и историю изменения некоторых экономических показателей. Для простоты все свойства компании — строки. Экономические показатели — большие целые числа и числа с плавающей точкой, а также даты. Предположим, что где-то в недрах микросервисного backend-да есть сервис-генератор подобных отчетов, например по id компании. Однако, поскольку id нет смысла выводить пользователю, идентификатор отсутствует в самой модели отчета.
Аналитик, в свою очередь, выдает задачу с феноменально точным описанием — «Сгенерировать excel отчет на базе данных MarketReport». Что ж, для нашего примера, создадим заглушку — генератор фейковых данных:

Первый запуск
Подключим EPPlus версии 4.5.3.3 и создадим базовую обвязку для будущего генератора.
Сердцем генератора будет метод Generate. ExcelPackage это модель документа, через которую мы и будем осуществлять все взаимодействия с ним. Также имеется конструктор для передачи пути к файлу или потока.
В методе main создается генератор отчетов, а также генератор Excel файлов. Далее полученный файл просто записывается на диск.
При попытке запустить приложение, получаем exception: InvalidOperationException: The workbook must contain at least one worksheet
Все правильно, Excel документ не может существовать без страниц, должна быть хотя бы одна. Добавляем ее, все интуитивно понятно:
var sheet = package.Workbook.Worksheets .Add("Market Report");
Запускаем снова и. вот оно! Теперь наше приложение генерирует документ и, хотя там еще ничего нет, он уже весит 2,5KB — значит мы работаем с Excel правильно и все идет как надо.
Вывод данных
Давайте выведем основную информацию по компании в шапку. Для доступа к конкретной ячейки объект Cells на странице пакета снабжен удобным индексатором. При этом, до конкретной ячейки можно достучаться как через номер строки и столбца, так и по привычному всем буквенно-числовому коду:
sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name;
Полный код вывода шапки.
sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name; sheet.Cells["B3"].Value = "Location:"; sheet.Cells["C3"].Value = $", " + $", " + $""; sheet.Cells["B4"].Value = "Sector:"; sheet.Cells["C4"].Value = report.Company.Sector; sheet.Cells["B5"].Value = report.Company.Description;
Для вывода исторических данных понадобится как минимум шапка таблицы и цикл по массиву History:
sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][] < new []>); var row = 9; var column = 2; foreach (var item in report.History)
Предлагаю обратить внимание на метод LoadFromArrays, который заполняет диапазон ячеек рваным(зубчатым) массивом. Здесь мы можем видеть, что типизация теряется и передавая массив object мы ожидаем что EPPlus в конечном итоге использует ToString, чтобы записать переданное в ячейки.
Стилизация
Если вы прямо сейчас откроете документ, то вы возможно увидите не то, что хотелось бы отдать в продакшн в пятницу вечером.
Как это выглядит

Во-первых, шапка никак не выделяется, во-вторых таблица не имеет границ. выравнивание пляшет, даты отображаются магическими числами, а капитализация «уходит в какую-то математику» — как это прокомментировал аналитик.
Да, на все эти красивости у нас уйдет больше года кода, чем на сам вывод данных, и, в конечном тоге, получившаяся каша из логики вывода данных и разметки заставит некоторых усомниться в их компетентности. но, мы же backend разработчики, так давайте сверстаем Excel Sheet!
Размер ячеек
Из коробки у нас есть возможность сделать автофит а так же вручную выставить ширину в соответствии с нашей ситуацией. А ситуация у нас не самая хорошая — по задумке аналитика в шапке у ячеек должен быть автофит, а у ячеек таблицы — тоже автофит. Так в чем же подвох?
Если вы когда-нибудь до этого открывали Excel, то возможно знаете, что ширина ячеек не может отличаться в рамках столбца и автофит будет по самому широкому контенту ячейки. Однако, простые вещи бывает нетак то просто объяснить. Но если вы справитесь, то вот как это будет выглядеть в коде:
sheet.Cells[1, 1, row, column + 2].AutoFitColumns(); sheet.Column(2).Width = 14; sheet.Column(3).Width = 12;
Формат данных
Как и большая часть стиля ячейки, он задается через одноименное свойство Style. Обратите внимание на вычисление 3-го аргумента индексатора. Это звоночек некачественного кода, но к этому мы вернемся в позже.
sheet.Cells[9, 4, 9 + report.History.Length, 4].Style.Numberformat.Format = "yyyy"; sheet.Cells[9, 2, 9 + report.History.Length, 2].Style.Numberformat.Format = "### ### ### ##0";
Выравнивание
Его можно задать как на ячейке, так и на диапазоне. На самом деле, для EPPlus, это одна и та же сущность — некий ExcelRange, описывающий диапазон ячеек, в том числе и со всего 1 ячейкой.
sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
Стиль текста
Также легко задается, используя Style.Font, кстати, здесь, на 2-й строчке, мы впервые указываем диапазон так, как привыкли его видеть пользователи Excel:
sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true; sheet.Cells["B2:C4"].Style.Font.Bold = true;
Границы
Задаем стиль линии, а также ее толщину. К этому моменту от кол-ва магических чисел-параметров индексатора уже рябит в глазах, но мы уже на финишной прямой. не так ли?
sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double); sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
График
«Ну что за отчет без графиков, верно, Карл?» — ловко подметит специалист по тестированию, и не важно, что этого не было в ТЗ а на часах уже половина 9-го.
Хотя график как сущность сам по себе сложнее таблиц и с графиками мы не работаем каждый день, EPPlus предоставляет довольно понятный API. Давайте добавим простейший график, отражающий рост капитализации:
var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line); capitalizationChart.Title.Text = "Capitalization"; capitalizationChart.SetPosition(7, 0, 5, 0); capitalizationChart.SetSize(800, 400); var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"])); capitalizationData.Header = report.Company.Currency;
Еще, может понадобиться защитить страницу от редактирования:
sheet.Protection.IsProtected = true;
На этом все, репозиторий с рабочим приложением находится здесь.
Заключение
О чем говорит финальная версия метода Generate?
public byte[] Generate(MarketReport report) < var package = new ExcelPackage(); var sheet = package.Workbook.Worksheets .Add("Market Report"); sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name; sheet.Cells["B3"].Value = "Location:"; sheet.Cells["C3"].Value = $", " + $", " + $""; sheet.Cells["B4"].Value = "Sector:"; sheet.Cells["C4"].Value = report.Company.Sector; sheet.Cells["B5"].Value = report.Company.Description; sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][] < new []>); var row = 9; var column = 2; foreach (var item in report.History) < sheet.Cells[row, column].Value = item.Capitalization; sheet.Cells[row, column + 1].Value = item.SharePrice; sheet.Cells[row, column + 2].Value = item.Date; row++; >sheet.Cells[1, 1, row, column + 2].AutoFitColumns(); sheet.Column(2).Width = 14; sheet.Column(3).Width = 12; sheet.Cells[9, 4, 9+ report.History.Length, 4].Style.Numberformat.Format = "yyyy"; sheet.Cells[9, 2, 9+ report.History.Length, 2].Style.Numberformat.Format = "### ### ### ##0"; sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; sheet.Column(4).Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true; sheet.Cells["B2:C4"].Style.Font.Bold = true; sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double); sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin; var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line); capitalizationChart.Title.Text = "Capitalization"; capitalizationChart.SetPosition(7, 0, 5, 0); capitalizationChart.SetSize(800, 400); var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"])); capitalizationData.Header = report.Company.Currency; sheet.Protection.IsProtected = true; return package.GetAsByteArray(); >
Во-первых, прежде всего, о том, что мы успешно справились с задачей, а именно, сгенерировали свой первый Excel отчет, поработали со стилями и даже решили пару попутных проблем.
Во-вторых, возможно имеет смысл искать новою работу, но, забегая вперед, я бы с этим не спешил. Если данная публикация наберет 1+ просмотров, то во второй части мы поговорим о том, как можно отделить стилизацию от логики заполнения данными, упростить манипуляции над ячейками и в целом сделаем код боле поддерживаемым.
Используем DataGrid в WPF
Не так давно мне потребовалось использовать таблицу в приложении на C#. По наивности я подумал, что буду использовать всё самое свежее и выбрал для проекта WPF вместо WinForm и, конечно, взял самый свежий .net framework 4.5. Тут-то и начались проблемы. Как всегда, я решил, что в Яндексе найдётся всё, но не тут то было — в интернете (вернее в РУнете) вообще ничего о DataGrid нет. Постоянно, как бы хитро не изменял запросы, я попадал на DataGridView из WinForm. Самое интересное в том, что платформа WPF полностью переработана и многое из того, что работало в WinForm в WPF не работает вообще никак.
Интересно также и то, что WPF должен быть как бы легче в использовании, ведь в Microsoft отделили дизайнеров от программистов, добавили векторную систему визуализации, язык XAML и прочее.
После продолжительного негодования я открыл большую и толстую книгу по WPF. И вот, чудо! Там было всё, как изменить размер, переименовать и перекрасить что угодно и как угодно, но о том, как элементарно достать запись из DataGrid не было и слова.
Отчаявшись, я решил было перейти на WinForm, но вспомнив старую добрую пословицу «если программа не работает, то проблема не в коде, а в программисте», решил разобраться в проблеме и (правда не помню на каком сайте) нашёл решение (это был сайт на английском языке в самых чёрных глубинах интернета). Название сайта вспомнил, ссылка в конце документа.
Привожу пример, как положить и достать информацию из DataGrid в WPF:
Для начала создадим новое приложение и в главном окне разместим элемент DataGrid, назовём его grid.
Далее создадим класс, который будет хранить информацию для таблицы:
class MyTable < public MyTable(int Id, string Vocalist, string Album, int Year) < this.Id = Id; this.Vocalist = Vocalist; this.Album = Album; this.Year = Year; >public int Id < get; set; >public string Vocalist < get; set; >public string Album < get; set; >public int Year < get; set; >>
После этого заполним таблицу с помощью события Loaded:
//Добавим информацию в таблицу private void grid_Loaded(object sender, RoutedEventArgs e) < Listresult = new List(3); result.Add(new MyTable(1, "Майкл Джексон", "Thriller", 1982)); result.Add(new MyTable(2, "AC/DC", "Back in Black", 1980)); result.Add(new MyTable(3, "Bee Gees", "Saturday Night Fever", 1977)); result.Add(new MyTable(4, "Pink Floyd", "The Dark Side of the Moon", 1973)); grid.ItemsSource = result; >
После всего получим информацию о строчке по событию MouseUp (клик мышкой):
//Получаем данные из таблицы private void grid_MouseUp(object sender, MouseButtonEventArgs e)
Заключение
Обратите внимание, что обращаться к элементам таблицы в WPF действительно легче, чем в WinForm (напомню, что в WinForm нужно обязательно указывать номер столбца и ячейки), более того, таблица сохраняет исходный тип данных и это очень удобно при их обработке.
Скриншот того, что получилось:

Полный исходный XAML код приложения (одна из вкусняшек WPF, можно посмотреть тип элемнта, его имя и подисанные на него события, а также прочую информацию):
Полный исходный код на языке C#:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace DataGrid < /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window < public MainWindow() < InitializeComponent(); >//Загрузка содержимого таблицы private void grid_Loaded(object sender, RoutedEventArgs e) < Listresult = new List(3); result.Add(new MyTable(1, "Майкл Джексон", "Thriller", 1982)); result.Add(new MyTable(2, "AC/DC", "Back in Black", 1980)); result.Add(new MyTable(3, "Bee Gees", "Saturday Night Fever", 1977)); result.Add(new MyTable(4, "Pink Floyd", "The Dark Side of the Moon", 1973)); grid.ItemsSource = result; > //Получаем данные из таблицы private void grid_MouseUp(object sender, MouseButtonEventArgs e) < MyTable path = grid.SelectedItem as MyTable; MessageBox.Show(" ID: " + path.Id + "\n Исполнитель: " + path.Vocalist + "\n Альбом: " + path.Album + "\n Год: " + path.Year); >> class MyTable < public MyTable(int Id, string Vocalist, string Album, int Year) < this.Id = Id; this.Vocalist = Vocalist; this.Album = Album; this.Year = Year; >public int Id < get; set; >public string Vocalist < get; set; >public string Album < get; set; >public int Year < get; set; >> >
Для примера использован Visual Studio 2012 и .net 4.0.
P.S. Не знаю, может, я рассказал очевидную для кого-то вещь, однако на это потратил довольно много времени, поэтому и решил поделиться.
How to create tables in windows form application?
How do you create a table in winforms where individual cells can look like this: I have been using a tablelayoutpanel, but it only lets you but one control per cell. Any ideas?
asked May 30, 2012 at 2:12
1,395 13 13 gold badges 39 39 silver badges 55 55 bronze badges
You can do this — you might need to create your own control but it is possible. A work around would be to have a SplitContainer control and 3 Panels
May 30, 2012 at 3:04
Look into .SpansRows and .SpansColumns properties for the control inside a cell.
Oct 28, 2019 at 11:59
2 Answers 2
The only way to emulate this is to use nested tablelayoutpanels.
To get your desired output in the example:
TableLayout1: 1 Row, 2 Columns TableLayout2 goes within column2 of TableLayout1: 3 Rows, 1 Column
answered May 30, 2012 at 2:21
Justin Pihony Justin Pihony
66.4k 18 18 gold badges 149 149 silver badges 182 182 bronze badges
There is ALWAYS a way to get it to work. It may not be obvious from the WinForms designer, but it is not too hard.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WindowsFormsApplication1 < partial class Form_1:Form < public Form_1() < InitializeComponent(); >/// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) < if (disposing && (components != null)) < components.Dispose(); >base.Dispose(disposing); > private void InitializeComponent() < this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.panel4 = new System.Windows.Forms.Panel(); this.panel3 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel(); this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // // tableLayoutPanel1 // this.tableLayoutPanel1.ColumnCount = 2; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 75F)); this.tableLayoutPanel1.Controls.Add(this.panel4, 3, 0); this.tableLayoutPanel1.Controls.Add(this.panel3, 2, 0); this.tableLayoutPanel1.Controls.Add(this.panel2, 1, 0); this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 0); this.tableLayoutPanel1.SetRowSpan(this.panel1, 3);//This line is the key. this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(527, 372); this.tableLayoutPanel1.TabIndex = 0; // // panel4 // this.panel4.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel4.Dock = System.Windows.Forms.DockStyle.Fill; this.panel4.Location = new System.Drawing.Point(134, 251); this.panel4.Name = "panel4"; this.panel4.Size = new System.Drawing.Size(390, 118); this.panel4.TabIndex = 4; // // panel3 // this.panel3.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel3.Dock = System.Windows.Forms.DockStyle.Fill; this.panel3.Location = new System.Drawing.Point(134, 127); this.panel3.Name = "panel3"; this.panel3.Size = new System.Drawing.Size(390, 118); this.panel3.TabIndex = 3; // // panel2 // this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel2.Dock = System.Windows.Forms.DockStyle.Fill; this.panel2.Location = new System.Drawing.Point(134, 3); this.panel2.Name = "panel2"; this.panel2.Size = new System.Drawing.Size(390, 118); this.panel2.TabIndex = 2; // // panel1 // this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(3, 3); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(125, 366); this.panel1.TabIndex = 1; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(527, 372); this.Controls.Add(this.tableLayoutPanel1); this.Name = "Form1"; this.Text = "Form1"; this.tableLayoutPanel1.ResumeLayout(false); this.ResumeLayout(false); >private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Panel panel3; private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Panel panel4; > >
The key to get this to work is this line:
this.tableLayoutPanel1.SetRowSpan(this.panel1, 3);//This line is the key.