Организация логики проекта
Изучаю шарп и друг подкинул идею по проекту, а именно система учета заказов багетной мастерской.
Я продумал логику(хз насколько правильная она или нет).Суть ее в том, что есть главный класс(вынесенный в отдельный файл класса) он является главным, описывает базовые функции расчетов заказов(сразу уточню, что заказы разного типа могут быть), потому что некоторые расчеты одинаковы для разных видов заказов, и я хотел дальше создать несколько форм по как раз таки категориям заказов, категории бы выбирались на форме по типу меню. После этого я хотел унаследоваться от главного класса, где уже есть шаблонные методы расчетов и добавить в наследников другие методы которые нужны соответствующему типу заказа.
В общем есть 4 textbox'a и label. С текстбоксов передаются значения(только цифры), в класс, который венесен в файл класса. В данном классе считается периметр, далее подставляется из текстбокса цена по оформлению и все это складывается и выводится на label, динамически, по изменению текстбоксов.
Далее в коде описан хендлер, который обрабатывает ввод только цифр(событие на keyPress). Далее вызывается событие, которое передает значения текстбоксов в класс, для расчетов.
private void keyEnter_textbox(object sender, KeyPressEventArgs e)
{
if (!(Char.IsDigit(e.KeyChar)) && !((e.KeyChar == ',') &&
(((TextBox)sender).Text.IndexOf(",") == -1) &&
(((TextBox)sender).Text.Length != 0)) && e.KeyChar != 8)
{ e.Handled = true; }
else { summ_label_textbox(sender, e); }
}
private void summ_label_textbox(object sender, EventArgs e)
{
label6.Text = new Order(textBox6.Text,textBox5.Text,textBox4.Text,textbox3.Text).BaguetteSumm().ToString();
}
Подскажите, как же организовывать логику передачи текстбоксов и вообще проекта в целом, чтобы я смог и вызывать методы класса без проблем, и чтобы события не создавали миллион объектов при обработке.
P.S. Как организовывать это все, если к примеру класс будет абстракт и наследники будут реализовывать свои методы с использованием методов базового класса. Как в этом плане все передавать, обращаться к методам. Просто хочу сразу делать логику по нормальному, чтобы не было говнокода потом.
Ответы (2 шт):
То что я сейчас покажу, сломает мозг, но зато потом настолько понравится, что вы не сможете без этого писать код. Это называется Привязка данных (Data Binding). Штука очень полезная, при чем настолько, что в WPF и других более мощных UI движках без нее в принципе невозможно ничего вменяемого написать. А в Winforms про нее знают единицы.
Как вы обычно пишете код:
- Создаете контрол
- Если в контроле что-то произошло, создаете обработчик
- В обработчике что-то считаете
- Записываете результат в другие контролы
Как пишут код с привязками данных
- Создаете контрол
- Создаете модель данных, реализующую интерфейс
INotifyPropertyChnaged - Привязываете контрол к свойству в модели данных, то есть сообщаете контролу, где брать данные для показа
- Меняете значение в модели данных, а контрол обновляется сам
Вы уже начали уносить из класса окна логику работы приложения в другие классы - отлично, значит привязки вам понравятся, так как они позволяют делать это очень легко.
К делу, вот реализация INotifyPropertyChnaged
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Выглядит страшновато, но пока с этим классом разбираться не надо, просто добавьте его в проект отдельным файлом.
Далее я начну писать приложение, которое будет считать периметр прямоугольника, оно содержит два контрола TextBox, в них я буду вводить вводные данные и один Label, который будет отображать результат.
Для этого, создам вот такую модель данных
public class Measures : NotifyPropertyChanged
{
private int _width;
private int _height;
private int _perimeter;
public int Width
{
get => _width;
set
{
_width = value;
OnPropertyChanged();
CalculatePerimeter();
}
}
public int Height
{
get => _height;
set
{
_height = value;
OnPropertyChanged();
CalculatePerimeter();
}
}
private void CalculatePerimeter()
{
Perimeter = Width + Height * 2;
}
public int Perimeter
{
get => _perimeter;
private set
{
_perimeter = value;
OnPropertyChanged();
}
}
}
Она наследует класс NotifyPropertyChanged, и из него использует метод OnPropertyChanged(), он как раз и нужен для того чтобы сообщать контролам, что данные изменились, и Windows Forms такой подход к разработке поддерживает нативно. То есть всю работу по обновлению контролов я отдаю самому UI движку Winforms, а сам концентрируюсь на написании полезного кода.
Чтобы всё было очевидно, контролы я создал прямо в коде конструктора. То есть дизайнер формы я вообще не трогал. А это значит, что просто создав новое Winforms приложение и скопировав код из этого ответа, вы сможете запустить и попробовать всё то что я здесь показал.
Код формы:
public partial class Form1 : Form
{
private Measures _data;
private TextBox widthInput;
private TextBox heightInput;
private Label resultLabel;
public Form1()
{
InitializeComponent();
FlowLayoutPanel panel = new FlowLayoutPanel() { Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown };
widthInput = CreateTextBox();
panel.Controls.Add(widthInput);
heightInput = CreateTextBox();
panel.Controls.Add(heightInput);
resultLabel = new Label() { AutoSize = true };
panel.Controls.Add(resultLabel);
Controls.Add(panel);
Load += Form1_Load;
}
private TextBox CreateTextBox()
{
TextBox result = new TextBox() { Margin = new Padding(5), MaxLength = 5 };
result.TextChanged += Result_TextChanged;
return result;
}
private void Result_TextChanged(object sender, EventArgs e)
{
if (sender is TextBox tbx)
{
tbx.BackColor = tbx.Text.Length == 0 || int.TryParse(tbx.Text, out _) ? SystemColors.Window : Color.LightPink;
}
}
private void BindControls(Measures data)
{
widthInput.DataBindings.Clear();
widthInput.DataBindings.Add(nameof(TextBox.Text), data, nameof(Measures.Width), false, DataSourceUpdateMode.OnPropertyChanged);
heightInput.DataBindings.Clear();
heightInput.DataBindings.Add(nameof(TextBox.Text), data, nameof(Measures.Height), false, DataSourceUpdateMode.OnPropertyChanged);
resultLabel.DataBindings.Clear();
resultLabel.DataBindings.Add(nameof(Label.Text), data, nameof(Measures.Perimeter));
}
private void Form1_Load(object sender, EventArgs e)
{
_data = new Measures();
BindControls(_data);
}
}
Обратите внимание, у меня вообще нет обработчиков событий, связанных с логикой приложения. Во время работы приложения будут функционировать только привязки данных.
Отвечая на вопрос про логику - вот собствено она и есть, класс для данных создается однажды и редактируется из интерфейса напрямую. При этом вы сможете создать новый класс с данными, снова вызвать BindControls для новой модели данных и готово. Эта модель легко сериализуется, сохраняется или читается из БД и т.д., в ней нет ничего лишнего.
Выглядит это так.
(4 + 6) * 2 = 20 всё верно.
Кстати, при вводе неверных данных никаких исключений не возникает, просто вычисления не производятся. Я добавил обработчик Result_TextChanged только для того чтобы показать пользователю, что ввод неверен, для этого я меняю фон текстбокса на розовый.
Советую так же поступить. Тем более проверки KeyPress не защищают от ввода неверных данных, я легко смогу вставить в текстбокс мусор из буфера обмена через Ctrl+V.
Поиграйте с этим проектом, посмотрите как работает. Потом решите, подходит оно или нет.
Ранее я рассказывал, как привязывать коллекции в Winforms: Привязка данных в DataGridView
Я зайду немного с другой стороны. Вы сейчас мыслите с точки зрения пользователя/дизайнера интерфейса, что с одной стороны неплохо, но на мой взгляд не на этапе старта/разработки проекта. Для начала возьмите ручку/карандаш и составьте список того, что у Вас есть (я не погружён в предметную область, но ниже то, что возникает в голове):
- багеты
- варианты/оформление/цвет
- ингридиенты (глютен, веган, кошер, халяль, диета)
- размеры
- кто сделал (пекарь, фея, сам купил)?
- и т.д. и т.п.
- заказы
- сроки поставки/доставки
- поставщик (курьер, служба доставки, самовывоз)
- метод оплаты
- скидка?
- промо?
- отменён?
- заказ от партнёра?
- и т.д. и т.п.
Список может показаться избыточным, но уверяю Вас, как только Вы начнёте собирать и анализировать данные (что есть/будет, что/как обрабатывать и что сохранять/отдавать) Вы так не подумаете. Определите также взаимосвязь между данными, какие данные обязательны, а какие опциональны. После того, как Вы это сделаете будет понятно какая "логика" Вам нужна, какие формы, контроллеры, сервисы и т.д. и т.п.
Хочу лишь донести до Вас, что создание программных продуктов нужно начинать не с визуального оформления, а с проработки предметной модели, т.е. осознания того, ЧТО нужно делать, КАКИМ образом обрабатывать поступившую и имеющуюся информацию и КАК (в каком виде) её сохранять/отображать.
Вы ведь скорее всего сталкивались с программами, которые доступны на нескольких платформах (Win, nix, Android, iOS) и даже для одной платформы могут быть несколько реализаций (WinFroms, WPF, Electron, etc.). Всё это возможно потому, что при разработке происходит разделение кода на слои:
- слой сохранения/предоставления данных
- слой содержащий саму логику
- слой отображения/взаимодействия с данными
Сейчас, Вам может показаться, что всё это слишком избыточно для Вашего маленького проекта и самое главное начать, но чем дальше Вы будете погружаться в него, чем больше изменений будете в него вносить, чем больше будете добавлять/изменять/удалять какие-нибудь функции, тем больше будете к этому приходить сами.
Написал отдельным ответом, так как много для комментария, а вопрошающий напомнил мне самого себя много лет назад.

