Форум программистов, компьютерный форум, киберфорум
Наши страницы
ashsvis
Войти
Регистрация
Восстановить пароль
"Орешек знаний тверд, Но все же мы не привыкли отступать..." (с)
Рейтинг: 5.00. Голосов: 1.

Каждый раз одно и то же

Запись от ashsvis размещена 11.04.2019 в 20:09
Метки c#, рефлексия

Когда начинаешь какую-нибудь вещицу, в которой много таблиц и справочников, то непременно хочется поскорее увидеть результат её работы.
Сначала создаёшь модель, какую-нибудь сущность, так, класс с набором свойств. Потом делаешь на основе него список.
Ну, список - это уже нужна табличка, кнопки по созданию новой записи, изменению текущей...
Для редактирования записи нужна форма. На форме размещаем надписи, редакторы значений, потом пишем код для заполнения этого хозяйства, обработку ввода...

И для каждой сущности так... Каждый раз одно и то же! И, главное, всё типовое - метки, редакторы, выбор значений из списка...

В общем, с этим надо что-делать.

Например, пишем приложение, где есть таблица сотрудников. Базовый класс выглядит примерно так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Employee : IComparable<Employee>
{
    //Ключевое поле
    public Guid IdEmployee { get; set; } = Guid.NewGuid();
    //Фамилия
    public string Surname { get; set; }
    //Имя
    public string FirstName { get; set; }
    //Отчество
    public string LastName { get; set; }
    //Должность
    public Guid IdAppointment { get; set; }
 
    public int CompareTo(Employee other)
    {
        return string.Compare(this.ToString(), other.ToString());
    }
 
    public override string ToString()
    {
        return $"{Surname} {FirstName} {LastName}";
    }
}
... и хочется сразу получить примерно такое:
Нажмите на изображение для увеличения
Название: Желаемая форма ввода.png
Просмотров: 158
Размер:	10.8 Кб
ID:	5326

Что же, благодаря рефлексии это можно сделать!

Для создания и вызова формы редактирования напишем несколько строк:
C#
1
2
3
4
5
6
7
8
9
10
btnAdd.Click += (o, e) =>
{
   // создаём пустой объект требуемого типа
   var item = Activator.CreateInstance(userClass.GetType());
   // вызываем диалог для заполнения свойств объекта
   var frm = PropertyPanelBuilder.ShowPropertyFormDialog(userModel, item);
   // если не была нажата клавиша "Ввод", выходим
   if (frm.DialogResult != DialogResult.OK) return;
   ...
}
где userClass и userModel - это параметры типа object, ссылки на экземпляры некоторых типов.
Допустим, через userClass передаётся объект типа Employee, а через userModel - корневой объект модели,
который, допустим, содержит, в том числе свойство типа List<Employee> с именем Employees.

Метод ShowPropertyFormDialog() выглядит следующим образом:
C#
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
26
27
28
29
/// <summary>
/// Показ формы с полями для класса параметра
/// </summary>
/// <param name="userClass">Класс для формирования полей редактирования</param>
/// <returns>Объект формы диалога</returns>
public static Form ShowPropertyFormDialog(object userModel, object userClass, string fontName = "Microsoft Sans Serif", float fontSize = 8.25f)
{
    // заготовка для формы диалога
    var frm = new Form()
    {
        Text = GetClassCaption(userClass),
        AutoSize = true,                                // размер подстраивается под содержимое
        AutoSizeMode = AutoSizeMode.GrowAndShrink,      // автоматически увеличивает или уменьшает размер
        StartPosition = FormStartPosition.CenterParent, // показывает форму по центру родительского окна
        FormBorderStyle = FormBorderStyle.FixedDialog,  // формат рамки формы
        MaximizeBox = false,                            // не показывать кнопку "Развернуть"
        MinimizeBox = false,                            // не показывать кнопку "Свернуть"
        ShowInTaskbar = false                           // не показывать в панели задач
    };
    frm.Font = new Font(fontName, fontSize);
    // создаём заготовку панели свойств 
    var ppb = new PropertyPanelBuilder(frm);
    // размещаем панель на форме
    frm.Controls.Add(ppb.BuildPropertyPanel(userModel, userClass));
    // показываем форму диалога
    frm.ShowDialog();
    // возвращаем ссылку на объект формы
    return frm;
}
Пока ничего супер сложного. Создаём объект формы, настраиваем её как диалоговую форму (сколько тысяч раз я это делал каждый раз?).
В качестве содержимого формы будет динамически создаваемая панель свойств, которую и размещаем на форме.
Потом показываем диалог. Всё.

Что же внутри BuildPropertyPanel()?
Кликните здесь для просмотра всего текста

C#
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/// <summary>
/// Построение панели свойств по ссылке на экземпляр класса
/// </summary>
/// <param name="userClass">Ссылка на экземпляр класса</param>
/// <returns>Ссылка на панель</returns>
public UserControl BuildPropertyPanel(object userModel, object userClass)
{
    // накопительный список действий, выполняемых при возврате изменённого значения в свойство объекта класса userClass
    var actions = new List<Action>();
    // кнопка "Ввод" панели свойств для диалога формы
    var btnOk = new Button
    {
        Text = "Ввод",
        DialogResult = DialogResult.None,   // специально не содержит закрытия по умолчанию
        Enabled = false,                    // изначально запрещена
        Anchor = AnchorStyles.Right,        // прижимается к правой стороне
        AutoSize = true,                    // увеличивается, если текст на кнопке не помещается
        Tag = _form                         // здесь передаётся ссылка на форму диалога, для обработчика нажатия
    };
    // кнопка "Отмена" панели свойств для диалога формы
    var btnCancel = new Button
    {
        Text = "Отменить",
        DialogResult = DialogResult.Cancel, // содержит по умолчанию отмену редактирования и закрытие формы диалога
        Anchor = AnchorStyles.Right,        // прижимается к правой стороне
        AutoSize = true                     // увеличивается, если текст на кнопке не помещается
    };
    // заготовка для панели свойств
    var userControl = new UserControl
    {
        AutoSize = true,                            // размер подстраивается под содержимое
        AutoSizeMode = AutoSizeMode.GrowAndShrink   // автоматически увеличивает или уменьшает размер
    };
    var type = userClass.GetType();         // получаем тип объекта, переданного через параметр
    MemberInfo[] m = type.GetProperties();  // получаем массив свойств объекта
    // создаём сетку для компоновки
    var grid = new TableLayoutPanel()
    {
        Padding = new Padding(10),                  // внешний отступ от краёв сетки, для красоты
        AutoSize = true,                            // размер подстраивается под содержимое
        AutoSizeMode = AutoSizeMode.GrowAndShrink,  // автоматически увеличивает или уменьшает размер
        ColumnCount = 2                             // будет два столбца - слева название, справа - поле редактирования
    };
    // добавим два столбца в сетку, с автоматическим изменением размера (поведение по умолчанию)
    grid.ColumnStyles.Add(new ColumnStyle());
    grid.ColumnStyles.Add(new ColumnStyle());
    // добавим сетку на панель свойств
    userControl.Controls.Add(grid);
    // вначале строк в сетке нет
    var row = 0;            
    foreach (var info in m) // для каждого свойства из массива свойств
    {
        // получаем ссылку на свойство по его имени
        var prop = type.GetProperty(info.Name);
        // получаем наименование свойства из дескриптора
        var caption = GetPropertyCaption(userClass, prop);
        // метка поля редактирования (размещаемая в первом столбце сетки, слева)
        var label = new Label
        {
            Name = "lb" + prop.Name,    // имя метки, это имя свойства с префиксом "lb"
            // содержимое метки - наименование свойства из дескриптора, а если нет, то имя свойства из класса 
            Text = (string.IsNullOrWhiteSpace(caption) ? prop.Name : caption) + ":",
            Dock = DockStyle.Fill,                      // растягивается на всю возможную поверхность ячейки в сетке 
            TextAlign = ContentAlignment.MiddleRight,   // текст метки прижимается вправо и по центру вертикального размера
            AutoSize = true                             // размер подстраивается под содержимое
        };
        TextBox textBox;                // ссылка на редактор текстового свойства
        NumericUpDown numericUpDown;    // ссылка на редактор числового свойства
        ComboBox comboBox;              // ссылка на селектор значения
        DateTimePicker dateTimePicker;  // ссылка на редактор значения даты
        CheckBox checkBox;              // ссылка на редактор логического свойства
        // получим имя типа свойства как строку символов
        var typeName = prop.PropertyType.ToString();
        // для часто встречающихся случаев
        switch (typeName)
        {
            case "System.String": // для строковых свойств
                grid.RowCount = row + 1;            // указываем количество строк сетки
                grid.RowStyles.Add(new RowStyle()); // добавляем стиль строки сетки, с автоматическим изменением размера (поведение по умолчанию)
                grid.Controls.Add(label, 0, row);   // добавляем метку в первый столбец строки сетки
                // формируем редактор текстовой строки
                textBox = new TextBox
                {
                    Name = "tb" + prop.Name,                        // имя компонента, это имя свойства с перфиксом "tb"
                    Text = prop.GetValue(userClass)?.ToString(),    // заполняем текст значением из свойства
                    Dock = DockStyle.Fill,                          // занимает всю ячейку сетки
                    BorderStyle = BorderStyle.FixedSingle,          // стильная рамка
                    Width = 160,                                    // ширина текстового поля (так настроил)
                    TabIndex = row                                  // индекс перехода по табуляции
                };
                // устанавливаем количество символов значения из атрибута DataRangeAttibute
                SetRange(userClass, prop, textBox);
                // установка символа защиты пароля по атрибуту DataPassword
                var safeEditMode = SetPasswordMode(userClass, prop, textBox);
                // при изменении содержимого в текстовом поле кнопка "Ввод" становится доступной
                textBox.TextChanged += (o, e) => { btnOk.Enabled = true; };
                // добавляем редактор текста во второй столбец строки сетки
                grid.Controls.Add(textBox, 1, row);
                // добавляем обработчик для этого редактора в список действий
                actions.Add(safeEditMode ? PasswordValue(userClass, prop, textBox) : TextValue(userClass, prop, textBox));
                if (safeEditMode)
                {
                    row++;
                    grid.RowCount = row + 1;            // указываем количество строк сетки
                    grid.RowStyles.Add(new RowStyle()); // добавляем стиль строки сетки, с автоматическим изменением размера (поведение по умолчанию)
                    label = new Label
                    {
                        Name = "lbRepeat" + prop.Name,    // имя метки, это имя свойства с перфиксом "lb"
                                                            // содержимое метки - наименование свойства из дескриптора, а если нет, то имя свойства из класса 
                        Text = (string.IsNullOrWhiteSpace(caption) ? $"{prop.Name} (repeat):" : $"{caption} (ещё раз):"),
                        Dock = DockStyle.Fill,                      // растягивается на всю возможную поверхность ячейки в сетке 
                        TextAlign = ContentAlignment.MiddleRight,   // текст метки прижимается вправо и по центру вертикального размера
                        AutoSize = true                             // размер подстраивается под содержимое
                    };
                    grid.Controls.Add(label, 0, row);   // добавляем метку в первый столбец строки сетки
                    // формируем редактор повтора текстовой строки
                    var textBoxRepeat = new TextBox
                    {
                        Name = "tbRepeat" + prop.Name,                  // имя компонента, это имя свойства с перфиксом "tb"
                        Text = prop.GetValue(userClass)?.ToString(),    // заполняем текст значением из свойства
                        Dock = DockStyle.Fill,                          // занимает всю ячейку сетки
                        BorderStyle = BorderStyle.FixedSingle,          // стильная рамка
                        Width = 160,                                    // ширина текстового поля (так настроил)
                        TabIndex = row                                  // индекс перехода по табуляции
                    };
                    // устанавливаем количество символов значения из атрибута DataRangeAttibute
                    SetRange(userClass, prop, textBoxRepeat);
                    SetPasswordMode(userClass, prop, textBoxRepeat);
                    grid.Controls.Add(textBoxRepeat, 1, row);
                    // добавляем обработчик для этого редактора в список действий
                    actions.Add(() =>
                    {
                        if (string.Compare(textBox.Text, textBoxRepeat.Text) != 0)
                            throw new Exception("Значение паролей не совпадают!");
                    });
                }
                break;
            case "System.Int32":    // для целочисленных свойств
            case "System.Decimal":  // для свойств с ценой
                grid.RowCount = row + 1;            // указываем количество строк сетки
                grid.RowStyles.Add(new RowStyle()); // добавляем стиль строки сетки, с автоматическим изменением размера (поведение по умолчанию)
                grid.Controls.Add(label, 0, row);   // добавляем метку в первый столбец строки сетки
                // формируем редактор числового значения
                numericUpDown = new NumericUpDown
                {
                    Name = "nud" + prop.Name,                       // имя компонента, это имя свойства с перфиксом "nud"
                    Dock = DockStyle.Left,                          // прижимаем влево
                    TextAlign = HorizontalAlignment.Right,          // текст прижимаем вправо
                    BorderStyle = BorderStyle.FixedSingle,
                    DecimalPlaces = typeName == "System.Decimal" ? 2 : 0,       // количество знаков для копеек для decimal
                    Maximum = typeName == "System.Decimal" ? 1000000 : 32767,   // значения границ
                    Width = 70,
                    TabIndex = row
                };
                // устанавливаем диапазон изменения значения из атрибута DataRangeAttibute
                SetRange(userClass, prop, numericUpDown);
                // получаем текущее значение
                var value = Convert.ToDecimal(prop.GetValue(userClass));
                // если полученное значение в разрешенном диапазоне
                if (value >= numericUpDown.Minimum && value <= numericUpDown.Maximum)
                    numericUpDown.Value = value;    // присваиваем как значение редактора числа
                numericUpDown.TextChanged += (o, e) => { btnOk.Enabled = true; };
                numericUpDown.ValueChanged += (o, e) => { btnOk.Enabled = true; };
                // добавляем редактор чисел во второй столбец строки сетки
                grid.Controls.Add(numericUpDown, 1, row);
                // добавляем обработчик для этого редактора в список действий
                actions.Add(NumberValue(userClass, prop, numericUpDown));
                break;
            case "System.Guid":         // для ключевых значений свойств
                if (row == 0) break;    // принимаем, что если первым в классе объявлено свойство типа Guid - то это ключевое свойство и его не показываем
                grid.RowCount = row + 1;            // указываем количество строк сетки
                grid.RowStyles.Add(new RowStyle()); // добавляем стиль строки сетки, с автоматическим изменением размера (поведение по умолчанию)
                grid.Controls.Add(label, 0, row);   // добавляем метку в первый столбец строки сетки
                // формируем селектор ключевых значении
                comboBox = new ComboBox
                {
                    Name = "cb" + prop.Name,                        // имя компонента, это имя свойства с перфиксом "cb"
                    Dock = DockStyle.Fill,
                    Width = 160,
                    DropDownStyle = ComboBoxStyle.DropDownList,     // только список для выбора
                    TabIndex = row
                };
                FillCombobox(userModel, userClass, prop, comboBox);
                // при подтверждении выбора разрешаем кнопку "Ввод"
                comboBox.SelectionChangeCommitted += (o, e) => { btnOk.Enabled = true; };
                // добавляем селектор во второй столбец строки сетки
                grid.Controls.Add(comboBox, 1, row);
                // добавляем обработчик для этого редактора в список действий
                actions.Add(GuidValue(userClass, prop, comboBox));
                break;
            case "System.DateTime": // для свойств с датой
                grid.RowCount = row + 1;
                grid.RowStyles.Add(new RowStyle());
                grid.Controls.Add(label, 0, row);
                dateTimePicker = new DateTimePicker
                {
                    Name = "dtp" + prop.Name,
                    Text = prop.GetValue(userClass)?.ToString(),
                    Dock = DockStyle.Fill,
                    Width = 70,
                    TabIndex = row
                };
                dateTimePicker.TextChanged += (o, e) => { btnOk.Enabled = true; };
                dateTimePicker.ValueChanged += (o, e) => { btnOk.Enabled = true; };
                // добавляем редактор даты во второй столбец строки сетки
                grid.Controls.Add(dateTimePicker, 1, row);
                // добавляем обработчик для этого редактора в список действий
                actions.Add(DateTimeValue(userClass, prop, dateTimePicker));
                break;
            case "System.Boolean": // для логических свойств
                grid.RowCount = row + 1;
                grid.RowStyles.Add(new RowStyle());
                grid.Controls.Add(label, 0, row);
                checkBox = new CheckBox
                {
                    Name = "cb" + prop.Name,
                    Text = (bool)prop.GetValue(userClass) ? "Да" : "Нет",
                    Dock = DockStyle.Fill,
                    Width = 70,
                    TabStop = true,
                    TabIndex = row
                };
                // получаем текущее значение
                checkBox.Checked = Convert.ToBoolean(prop.GetValue(userClass));
                checkBox.CheckedChanged += (o, e) => 
                {
                    var ch = (CheckBox)o;
                    ch.Text = ch.Checked ? "Да" : "Нет";
                    btnOk.Enabled = true;
                };
                // добавляем редактор даты во второй столбец строки сетки
                grid.Controls.Add(checkBox, 1, row);
                // добавляем обработчик для этого редактора в список действий
                actions.Add(BooleanValue(userClass, prop, checkBox));
                break;
        }
        row++;
    }
 
    #region Панель кнопок "Ввод" и "Отмена"
            
    // создадим панель для размещения кнопок
    var flow = new FlowLayoutPanel
    {
        AutoSize = true,
        AutoSizeMode = AutoSizeMode.GrowAndShrink,
        Dock = DockStyle.Right,                     // прижимается вправо
    };
    // добавляем строку в сетку
    grid.RowCount = row + 1;
    grid.RowStyles.Add(new RowStyle()); // с авто размером
    grid.Controls.Add(flow, 0, row);    // в первый столбец
    grid.SetColumnSpan(flow, 2);        // с растягиванием на два столбца
    // настроим кнопке ввод индекс табуляции
    btnOk.TabIndex = row + 1;
    // добавим её на панель свойств
    flow.Controls.Add(btnOk);
    // добавим обработчик нажатия кнопки "Ввод"
    btnOk.Click += (o, e) => 
    {
        var frm = (Form)((Button)o).Tag;    // получим ссылку на форму диалога
        try
        {
            // перешлём все значения из полей редактирования в свойства объекта
            foreach (var act in actions) act();
            // если ошибок присвоения не было, закрываем форму с признаком ОК
            frm.DialogResult = DialogResult.OK;
        }
        catch (Exception ex)
        {
            // выводим сообщение при первой ошибке
            MessageBox.Show(frm, ex.Message, "Ошибка в значении свойства", MessageBoxButtons.OK, MessageBoxIcon.Error);
            // форма диалога при этом не закрывается
        }
    };
    // настроим кнопке ввод индекс табуляции
    btnCancel.TabIndex = row + 2;
    // добавим её на панель свойств
    flow.Controls.Add(btnCancel);
 
    #endregion
 
    return userControl;
}

Ну, а внутри всё и начинается... Сплошная рефлексия, понимаешь...

Для удобства работы пришлось подправить модельный класс:
C#
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
26
27
28
[Serializable]
[Description("Сотрудник")]
public class Employee : IComparable<Employee>
{
    public Guid IdEmployee { get; set; } = Guid.NewGuid();
 
    [Description("Фамилия"), DataNotEmpty]
    public string Surname { get; set; }
 
    [Description("Имя"), DataNotEmpty]
    public string FirstName { get; set; }
 
    [Description("Отчество"), DataNotEmpty]
    public string LastName { get; set; }
 
    [Description("Должность"), DataLookup("IdAppointment", "Appointments")]
    public Guid IdAppointment { get; set; }
 
    public int CompareTo(Employee other)
    {
        return string.Compare(this.ToString(), other.ToString());
    }
 
    public override string ToString()
    {
        return $"{Surname} {FirstName} {LastName}";
    }
}
Добавить атрибуты, из которых динамически берутся названия свойств, источники справочников, границы числовых значений...

В общем, кому интересно, заходите в репозиторий: https://github.com/ashsvis/SchoolOfficeWork
Размещено в Без категории
Просмотров 550 Комментарии 13
Всего комментариев 13
Комментарии
  1. Старый комментарий
    Аватар для Usaga
    Знакомая тема)

    Вроде бы подход хороший, но вот когда появится необходимость какую-то форму кастомизировать, то геморрой-то и начнётся)
    Запись от Usaga размещена 12.04.2019 в 08:19 Usaga вне форума
  2. Старый комментарий
    Аватар для Storm23
    Цитата:
    Вроде бы подход хороший, но вот когда появится необходимость какую-то форму кастомизировать, то геморрой-то и начнётся)
    Так автор это для быстрого прототипирования делает, вроде как.

    А так да, такой подход ломается как только нужно сделать что-то отличное от стандарта. А такое наступает практически сразу
    Запись от Storm23 размещена 12.04.2019 в 09:20 Storm23 вне форума
  3. Старый комментарий
    Аватар для ashsvis
    Цитата:
    когда появится необходимость какую-то форму кастомизировать
    Я говорю про общий подход к проблеме. Если сравнивать программирование с серийным выпуском каких-либо деталей,
    то серийное производство обычно позволяет сэкономить кучу времени за счёт применения стандартных решений.

    Можно больше времени отдать для модели, так как сокращается цикл разработки и сразу видишь, что чего-то не хватает, или что-то лишнее в модели.

    Однако никто не отменял "ручную работу", "авторское" исполнение, так сказать.
    Запись от ashsvis размещена 12.04.2019 в 10:54 ashsvis вне форума
  4. Старый комментарий
    Аватар для Usaga
    Да не, мы не против этого решения) Просто в реальной жизни оно мало применимо из-за необходимости хоть немного, да что-то по-своему сделать на каждой отдельной форме.
    Запись от Usaga размещена 12.04.2019 в 11:01 Usaga вне форума
  5. Старый комментарий
    Аватар для ashsvis
    А если на базе этого решения сделать генератор кода? Тогда кастомизировать можно ведь?
    Запись от ashsvis размещена 12.04.2019 в 11:24 ashsvis вне форума
  6. Старый комментарий
    Аватар для Usaga
    Нет. Типовое решение ограничивает возможности кастомизации. Чем больший простор для кастомизации заложен, тем сложнее реализация вашего... фреймворка, чтоли.

    В моей практике была поддержка подобного проекта, построенного на похожих рельсах. Только там декларативно формы описывались (XML) и UI был на базе WebForms.

    Когда нужно было добавить или расширить форму, чей функционал укладывался в возможности фреймворка, то делалось это играючи чуть ли не за пять минут. Но, когда возникала необходимость сделать что-то более интересное, то начинался такой геморрой адский, что сводил на нет все выигрыши по времени, полученные ранее.

    После этого я понял, что полностью автоматический подход - очень плохо. Некоторое количество boilerplate кода не такая уж и беда, на фоне решения, которое вместо того, чтобы помогать, тупо тебе мешает работать.
    Запись от Usaga размещена 12.04.2019 в 16:27 Usaga вне форума
  7. Старый комментарий
    Аватар для ashsvis
    Тогда нужно делать расширения для VS, типа мастеров, чтобы делали эту работу...
    Запись от ashsvis размещена 12.04.2019 в 21:01 ashsvis вне форума
  8. Старый комментарий
    Аватар для diadiavova
    А чем не устраивает инструмент "Источники данных"? С него можно перетащить на форму все что надо и получить все поля. Можно настроить отображение конкретных типов, редактировать форму как нужно и т.д.
    Запись от diadiavova размещена 13.04.2019 в 13:32 diadiavova вне форума
  9. Старый комментарий
    Аватар для ashsvis
    У меня больше контроля над моим кодом.
    Запись от ashsvis размещена 14.04.2019 в 12:34 ashsvis вне форума
  10. Старый комментарий
    Аватар для diadiavova
    Цитата:
    Сообщение от ashsvis Просмотреть комментарий
    У меня больше контроля над моим кодом.
    Исходя из заголовка поста "Каждый раз одно и то же", я решил, что ты решаешь типичную задачу. Как по мне, так данного инструмента для решения типичных задач более чем достаточно. В этой связи мне было бы любопытно узнать, чего именно в нем не хватает.
    Запись от diadiavova размещена 16.04.2019 в 08:22 diadiavova вне форума
  11. Старый комментарий
    Аватар для ashsvis
    Цитата:
    чего именно в нем не хватает...
    Не могу ответить, так как толком не использовал этот инструмент.
    Про свой же скажу, что при помощи атрибутов у свойств класса я определяю связь между значением поля (Guid) и списком ключевых значений другого класса. Эта связь преобразуется в приготовляемой форме в виде комбобокса и позволяет выбирать значения для свойства через выбор из списка значений. Есть ли такая штука у "Источника данных", я не мог проверить. Если есть, то замечательно (и я написал свой "велосипед" для этого случая). Но для меня польза в том, что мой код - вот он и я могу его совершенствовать как захочу...
    Запись от ashsvis размещена 17.04.2019 в 20:07 ashsvis вне форума
  12. Старый комментарий
    Аватар для diadiavova
    Для работы с датасетом "Источники данных" заточены немного лучше чем для работы с классами. Там за счет того, что таблицы уже имеют связи можно многое просто нащелкать в дизайнере. Иногда для задач немного посложнее приходится пару-тройку строк кода написать. Но в принципе, даже если работаешь с классами модели, то все равно ручного кода будет меньше, чем если реализовывать все самому. С другой стороны - дело вкуса, конечно.
    Запись от diadiavova размещена 18.04.2019 в 08:09 diadiavova вне форума
  13. Старый комментарий
    Аватар для ashsvis
    Привожу пример применения генератора форм для приложения по-сложнее:
    https://github.com/ashsvis/DryCleaning
    Обратите также внимание на классы быстрого создания отчётов.
    Запись от ashsvis размещена 29.04.2019 в 18:30 ashsvis вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.