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;
} |