Предположим, что нужно обрабатывать набор некоторых данных представляемых таблицей. У каждой таблицы есть заголовок, пользователь может добавлять произвольное число таблиц и редактировать данные в них. В качестве примера обработки возьмем простую сумму чисел.
Начинаем с того, что описываем "модель" - все то, что относится к данным и их обработке. Тут всего два класса -
SomeClass, который представляет собой данные из таблицы:
public class SomeClass
{
public int M { get { return Data.GetLength(1); } }
public int N { get { return Data.GetLength(0); } }
public double[,] Data { get; }
public SomeClass(double[,] data)
{
Data = new double[data.GetLength(0), data.GetLength(1)];
Array.Copy(data, Data, data.Length);
}
public double Sum()
{
var s = 0.0;
foreach (var x in Data)
s += x;
return s;
}
}
и класс
Item, который отвечает за таблицу. Тут все просто - таблица это сами данные и заголовок.
public class Item
{
public string Title { get; }
public SomeClass Data { get; }
public Item(string title, SomeClass sc)
{
Title = title;
if (sc != null)
Data = new SomeClass(sc.Data);
}
public override string ToString() => Title;
}
Теперь представление для ячейки (одной таблицы). В таких случаях удобно создавать UserControl, назовем его
ItemView
Определим в нем два метода -
Fill и
Extract.
Fill - отвечает за то чтобы отобразить указанный элемент.
Extract - соберет информацию из элементов управления и вернет экземпляр класса Item.
Таблицу можно описать в виде следующих элементов управления - двух
TextBox для указания размерности (количество строк, столбцов) и
DataGridView для заполнения значений.
public void Fill(Item item)
{
label1.Text = item.Title;
if (item.Data != null)
{
var n = item.Data.N;
var m = item.Data.M;
textBox1.Text = n.ToString();
textBox2.Text = m.ToString();
dataGridView1.ColumnCount = m;
dataGridView1.RowCount = n;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
dataGridView1[j, i].Value = item.Data.Data[i, j];
}
else
{
textBox1.Text = "";
textBox2.Text = "";
dataGridView1.ColumnCount = 0;
dataGridView1.RowCount = 0;
}
}
и реализация метода Extract:
public Item Extract()
{
string title = label1.Text;
if (string.IsNullOrWhiteSpace(title))
return null;
var n = dataGridView1.RowCount;
var m = dataGridView1.ColumnCount;
if (n == 0 || m == 0)
return new Item(title, null);
var data = new double[n, m];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
data[i, j] = Convert.ToDouble(dataGridView1[j, i].Value);
return new Item(title, new SomeClass(data));
}
Код для задания размерности таблицы и генерации произвольных значений опускаю, чтобы излишне не раздувать ответ.
Теперь практически все есть, можно переходить к созданию главной формы.
Здесь понадобится
ListBox для навигации между таблицами, панель, в которой будет размещаться UserControl, кнопка для расчетов и Label для вывода результатов.
Чтобы связать модель с представлением воспользуемся классов BindingList:
ItemView iview;
BindingSource bs = new BindingSource()
{ DataSource = typeof(Item) };
public Form1()
{
InitializeComponent();
listBox1.DataSource = bs;
bs.PositionChanged += PositionChanged;
}
PositionChanged - обработчик переключений между таблицами. Здесь нам нужно сохранять информацию в таблицу по старом индексу и отображать данные для новой.
private void SaveCurrent()
{
var pr = iview.Extract();
if (pr != null)
{
var old = bs.List.OfType<Item>().First(v => v.Title == pr.Title);
var ind = bs.List.IndexOf(old);
bs[ind] = pr;
}
}
private void PositionChanged(object sender, EventArgs e)
{
if (bs.Current is Item item && bs.Count > 1)
{
SaveCurrent();
iview.Fill(item);
}
}
Здесь в методе SaveCurrent() мы последовательно выполняем следующие шаги:
1. Извлекаем данные
2. Ищем таблицу по указанному заголовку
3. Ищем индекс в списке
4. Обновляем таблицу
и код для расчетов:
SaveCurrent();
var items = bs.OfType<Item>().Select(item => item.Data?.Sum() ?? -1);
resLbl.Text = string.Join(";", items);
Здесь тоже вызывается метод SaveCurrent() для принудительного сохранения при расчете.
Итого, результат примерно следующий:
Код может быть не оптимальным, так как я в основном использую F# а не C# а также WPF а не WinForms, но главное тут суть подхода ;-)