Compare commits

..

9 Commits

Author SHA1 Message Date
bekodeg
e825cc46c2 Добавил каталог 2024-11-07 15:54:02 +04:00
bekodeg
09a8bc1857 Добил добавление и изменение 2024-11-07 14:04:13 +04:00
bekodeg
2c0e6b13f5 Добавление записи работает 2024-11-07 13:41:24 +04:00
bekodeg
8563387642 Реализация репозитория для справочной таблицы 2024-11-06 23:20:09 +04:00
bekodeg
d9abe59250 Сверстал первые формы, настроил di 2024-11-06 23:06:47 +04:00
bekodeg
22041e42bc Dal 2024-11-06 21:04:19 +04:00
bekodeg
cea8e833e8 Изменения на паре 2024-11-04 13:04:34 +04:00
bekodeg
2d50eff660 3 компонент 2024-10-28 20:24:52 +04:00
bekodeg
098e5a1d3c Сделал 2 компонент 2024-10-25 22:29:47 +04:00
80 changed files with 4525 additions and 3 deletions

304
.editorconfig Normal file
View File

@ -0,0 +1,304 @@
# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии
root = true
# Файлы C#
[*.cs]
#### Основные параметры EditorConfig ####
# Отступы и интервалы
indent_size = 4
indent_style = space
tab_width = 4
# Предпочтения для новых строк
end_of_line = crlf
insert_final_newline = false
#### Рекомендации по написанию кода .NET ####
# Упорядочение Using
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# Предпочтения для this. и Me.
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
# Параметры использования ключевых слов языка и типов BCL
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Предпочтения для скобок
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
# Предпочтения модификатора
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Выражения уровень предпочтения
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true:warning
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true
# Предпочтения для полей
dotnet_style_readonly_field = true
# Настройки параметров
dotnet_code_quality_unused_parameters = all
# Параметры подавления
dotnet_remove_unnecessary_suppression_exclusions = none
# Предпочтения для новых строк
dotnet_style_allow_multiple_blank_lines_experimental = false:warning
dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
#### Рекомендации по написанию кода C# ####
# Предпочтения var
csharp_style_var_elsewhere = false:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
# Члены, заданные выражениями
csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_lambdas = true:warning
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_expression_bodied_operators = true:suggestion
csharp_style_expression_bodied_properties = true:warning
# Настройки соответствия шаблонов
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true:warning
# Настройки проверки на null
csharp_style_conditional_delegate_call = true
# Предпочтения модификатора
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# Предпочтения для блоков кода
csharp_prefer_braces = true:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped:warning
csharp_style_prefer_method_group_conversion = true:warning
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true:warning
# Выражения уровень предпочтения
csharp_prefer_simple_default_expression = true:warning
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_tuple_swap = true:warning
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# предпочтения для директивы using
csharp_using_directive_placement = outside_namespace:warning
# Предпочтения для новых строк
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
csharp_style_allow_embedded_statements_on_same_line_experimental = true:warning
#### Правила форматирования C# ####
# Предпочтения для новых строк
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Предпочтения для отступов
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Предпочтения для интервалов
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Предпочтения переноса
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Стили именования ####
# Правила именования
dotnet_naming_rule.asyncmethods_should_be_pascalcaseasync.severity = warning
dotnet_naming_rule.asyncmethods_should_be_pascalcaseasync.symbols = asyncmethods
dotnet_naming_rule.asyncmethods_should_be_pascalcaseasync.style = pascalcaseasync
dotnet_naming_rule.staticasyncmethods_should_be_pascalcaseasync.severity = warning
dotnet_naming_rule.staticasyncmethods_should_be_pascalcaseasync.symbols = staticasyncmethods
dotnet_naming_rule.staticasyncmethods_should_be_pascalcaseasync.style = pascalcaseasync
dotnet_naming_rule.constmember_should_be_constant_case.severity = warning
dotnet_naming_rule.constmember_should_be_constant_case.symbols = constmember
dotnet_naming_rule.constmember_should_be_constant_case.style = constant_case
dotnet_naming_rule.genericparametrs_should_be_typepascalcase.severity = warning
dotnet_naming_rule.genericparametrs_should_be_typepascalcase.symbols = genericparametrs
dotnet_naming_rule.genericparametrs_should_be_typepascalcase.style = typepascalcase
dotnet_naming_rule.localmembers_should_be_camelcase.severity = warning
dotnet_naming_rule.localmembers_should_be_camelcase.symbols = localmembers
dotnet_naming_rule.localmembers_should_be_camelcase.style = camelcase
dotnet_naming_rule.privatfealds_should_be__privatcamalcase.severity = warning
dotnet_naming_rule.privatfealds_should_be__privatcamalcase.symbols = privatfealds
dotnet_naming_rule.privatfealds_should_be__privatcamalcase.style = _privatcamalcase
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
# Спецификации символов
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.asyncmethods.applicable_kinds = method
dotnet_naming_symbols.asyncmethods.applicable_accessibilities = *
dotnet_naming_symbols.asyncmethods.required_modifiers = async
dotnet_naming_symbols.staticasyncmethods.applicable_kinds = method, local_function
dotnet_naming_symbols.staticasyncmethods.applicable_accessibilities = *
dotnet_naming_symbols.staticasyncmethods.required_modifiers = async, static
dotnet_naming_symbols.constmember.applicable_kinds = property, field
dotnet_naming_symbols.constmember.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.constmember.required_modifiers = const
dotnet_naming_symbols.localmembers.applicable_kinds = property, field, parameter, local, method
dotnet_naming_symbols.localmembers.applicable_accessibilities = local
dotnet_naming_symbols.localmembers.required_modifiers =
dotnet_naming_symbols.privatfealds.applicable_kinds = property, field
dotnet_naming_symbols.privatfealds.applicable_accessibilities = private
dotnet_naming_symbols.privatfealds.required_modifiers =
dotnet_naming_symbols.genericparametrs.applicable_kinds = type_parameter
dotnet_naming_symbols.genericparametrs.applicable_accessibilities = *
dotnet_naming_symbols.genericparametrs.required_modifiers =
# Стили именования
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.constant_case.required_prefix =
dotnet_naming_style.constant_case.required_suffix =
dotnet_naming_style.constant_case.word_separator = _
dotnet_naming_style.constant_case.capitalization = all_upper
dotnet_naming_style._privatcamalcase.required_prefix = _
dotnet_naming_style._privatcamalcase.required_suffix =
dotnet_naming_style._privatcamalcase.word_separator =
dotnet_naming_style._privatcamalcase.capitalization = camel_case
dotnet_naming_style.pascalcaseasync.required_prefix =
dotnet_naming_style.pascalcaseasync.required_suffix = Async
dotnet_naming_style.pascalcaseasync.word_separator =
dotnet_naming_style.pascalcaseasync.capitalization = pascal_case
dotnet_naming_style.typepascalcase.required_prefix = T
dotnet_naming_style.typepascalcase.required_suffix =
dotnet_naming_style.typepascalcase.word_separator =
dotnet_naming_style.typepascalcase.capitalization = pascal_case

View File

@ -5,7 +5,13 @@ VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cop.Borovkov.Var3", "Cop.Borovkov.Var3\Cop.Borovkov.Var3.csproj", "{A8604186-0CDE-4504-805B-46104141269A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCustomComponents", "TestCustomComponents\TestCustomComponents.csproj", "{E2C46D08-ACCE-4547-922B-E92AD76D99C8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestCustomComponents", "TestCustomComponents\TestCustomComponents.csproj", "{E2C46D08-ACCE-4547-922B-E92AD76D99C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PIHelperSh.PdfCreator", "PIHelperSh.PdfCreater\PIHelperSh.PdfCreator.csproj", "{572BD835-A500-43C9-A66F-648540F4A1C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab3", "Lab3\Lab3.csproj", "{1E630CC7-090F-471C-ADA1-74107CF3DC2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab3.Database", "Lab3.Database\Lab3.Database.csproj", "{698DE9E8-7885-4F98-AFE3-9A9C6CD2FCF5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,6 +27,18 @@ Global
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Release|Any CPU.Build.0 = Release|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Release|Any CPU.Build.0 = Release|Any CPU
{1E630CC7-090F-471C-ADA1-74107CF3DC2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E630CC7-090F-471C-ADA1-74107CF3DC2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E630CC7-090F-471C-ADA1-74107CF3DC2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E630CC7-090F-471C-ADA1-74107CF3DC2A}.Release|Any CPU.Build.0 = Release|Any CPU
{698DE9E8-7885-4F98-AFE3-9A9C6CD2FCF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{698DE9E8-7885-4F98-AFE3-9A9C6CD2FCF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{698DE9E8-7885-4F98-AFE3-9A9C6CD2FCF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{698DE9E8-7885-4F98-AFE3-9A9C6CD2FCF5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,36 @@
namespace Cop.Borovkov.Var3.Components
{
partial class CustomPdfHistogram
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

View File

@ -0,0 +1,68 @@
using Cop.Borovkov.Var3.Models;
using PIHelperSh.PdfCreator;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Models.PieChartModel;
using System.ComponentModel;
using static System.Runtime.InteropServices.Marshalling.IIUnknownCacheStrategy;
namespace Cop.Borovkov.Var3.Components
{
public partial class CustomPdfHistogram : Component
{
public CustomPdfHistogram()
{
InitializeComponent();
}
public CustomPdfHistogram(IContainer container)
{
container.Add(this);
InitializeComponent();
}
/// <summary>
/// Сохранить гистограмму в пдф
/// </summary>
/// <param name="histogramInfo"></param>
public void SaveToPdf(PdfHistigramInfo histogramInfo)
{
if (string.IsNullOrEmpty(histogramInfo.FilePath))
throw new ArgumentException("Путь к файлу не должен быть пустым.");
if (string.IsNullOrEmpty(histogramInfo.DocumentTitle))
throw new ArgumentException("Название документа не должно быть пустым.");
if (string.IsNullOrEmpty(histogramInfo.HistogramTitle))
throw new ArgumentException("Заголовок диаграммы не должен быть пустым.");
if (histogramInfo.Values == null || histogramInfo.Values.Count() == 0)
throw new ArgumentException("Набор данных не должен быть пустым.");
foreach (var data in histogramInfo.Values)
{
if (string.IsNullOrEmpty(data.Name) || data.Points == null || data.Points.Count() == 0)
throw new ArgumentException($"Набор данных для серии '{data.Name}' некорректен.");
}
PdfCreator creator = new PdfCreator();
creator.AddParagraph(new()
{
Style = PdfStyleType.Title,
Text = histogramInfo.DocumentTitle,
MarginAfter = PdfMargin.Smal,
});
creator.AddHistogram(new()
{
ChartName = histogramInfo.HistogramTitle,
LegendPosition = histogramInfo.LegendPosition,
DataSet = histogramInfo.Values.Select(l => new PdfHistogramData()
{
DisplayName = l.Name,
Value = l.Points.Select(p => (p.Name, p.Value))
}).ToList()
});
creator.SavePdf(histogramInfo.FilePath);
}
}
}

View File

@ -0,0 +1,36 @@
namespace Cop.Borovkov.Var3.Components
{
partial class CustomPdfTable
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

View File

@ -0,0 +1,92 @@
using Cop.Borovkov.Var3.Models;
using PIHelperSh.PdfCreator;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using PIHelperSh.PdfCreator.Models.TableModels;
using System.ComponentModel;
namespace Cop.Borovkov.Var3.Components
{
/// <summary>
/// Компонент для сохранения таблицы в пдф
/// </summary>
public partial class CustomPdfTable : Component
{
/// <summary>
/// </summary>
public CustomPdfTable()
{
InitializeComponent();
}
/// <summary>
/// </summary>
/// <param name="container"></param>
public CustomPdfTable(IContainer container)
{
container.Add(this);
InitializeComponent();
}
/// <summary>
/// Сохранить набор таблиц в пдф
/// </summary>
/// <param name="fileName"></param>
/// <param name="title"></param>
/// <param name="tables"></param>
public void SaveToPdf(PdfTableInfo tableInfo)
{
if (!tableInfo.Tables.Any())
{
return;
}
PdfCreator creator = new PdfCreator();
creator.AddParagraph(new()
{
Style = PdfStyleType.Title,
Text = tableInfo.Title,
MarginAfter = PdfMargin.Smal,
});
foreach (string[,] table in tableInfo.Tables)
{
List<PDFSimpleTableRow> rows = new();
for (int i = 0; i < table.GetLength(0); ++i)
{
PDFSimpleTableRow row = new();
for (int j = 0; j < table.GetLength(1); ++j)
{
row.Items.Add(table[i, j]);
}
rows.Add(row);
}
creator.AddSimpleTable(new()
{
Header = rows.First().Items.Select(item => new PdfTableColumn()
{
Title = item,
Size = 3,
} as IPdfColumnItem).ToList(),
Rows = rows.Skip(1).ToList(),
HeaderStyle = PdfStyleType.Basic,
HeaderHorizontalAlignment = PdfAlignmentType.Left,
RowHorizontalAlignment = PdfAlignmentType.Left,
});
creator.AddParagraph(new()
{
MarginAfter = PdfMargin.Smal,
});
}
creator.SavePdf(tableInfo.FilePath);
}
}
}

View File

@ -0,0 +1,36 @@
namespace Cop.Borovkov.Var3.Components
{
partial class CustomPdfTableWithGrouping
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

View File

@ -0,0 +1,73 @@
using Cop.Borovkov.Var3.Models;
using PIHelperSh.PdfCreator.Models.TableModels;
using PIHelperSh.PdfCreator;
using System.ComponentModel;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
namespace Cop.Borovkov.Var3.Components
{
/// <summary>
/// Компонент создающий таблицу и группирует элементы по 1 столбцу
/// </summary>
public partial class CustomPdfTableWithGrouping : Component
{
/// <summary>
/// </summary>
public CustomPdfTableWithGrouping()
{
InitializeComponent();
}
/// <summary>
/// </summary>
/// <param name="container"></param>
public CustomPdfTableWithGrouping(IContainer container)
{
container.Add(this);
InitializeComponent();
}
/// <summary>
/// Сохранить набор таблиц в пдф
/// </summary>
/// <param name="fileName"></param>
/// <param name="title"></param>
/// <param name="tables"></param>
public void SaveToPdf<TType>(PdfTableWithGroupingInfo<TType> tableInfo) where TType : class
{
if (!tableInfo.Columns.Any() || !tableInfo.Rows.Any())
{
return;
}
PdfCreator creator = new PdfCreator();
creator.AddParagraph(new()
{
Style = PdfStyleType.Title,
Text = tableInfo.Title,
MarginAfter = PdfMargin.Smal,
});
creator.AddTable(new PdfTable<TType>()
{
Header = tableInfo.Columns.Select(c => new PdfTableColumn()
{
Title = c.Header,
Size = c.Width,
PropertyName = c.PropertyName,
} as IPdfColumnItem).ToList(),
Records = tableInfo.Rows
.OrderBy(r => typeof(TType)
.GetProperty(tableInfo.Columns.First().PropertyName)!.GetValue(r.Value))
.Select(r => (r.Value, r.Height))
.ToList(),
HeaderHeaight = tableInfo.HeaderHeight,
});
creator.SavePdf(tableInfo.FilePath);
}
}
}

View File

@ -5,6 +5,13 @@
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>$(AssemblyName)</PackageId>
<Version>$(VersionPrefix)8.0.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PIHelperSh.PdfCreater\PIHelperSh.PdfCreator.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,18 @@
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Точка
/// </summary>
public record ChartPoint
{
/// <summary>
/// Имя
/// </summary>
public required string Name { get; init; } = string.Empty;
/// <summary>
/// Значение
/// </summary>
public required double Value { get; init; }
}
}

View File

@ -0,0 +1,9 @@
namespace Cop.Borovkov.Var3.Models
{
public record ColumnInfo
{
public string Header { get; init; } = null!;
public string PropertyName { get; init; } = null!;
public float Width { get; init; } = 3;
}
}

View File

@ -0,0 +1,35 @@
using PIHelperSh.PdfCreator.Enums;
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Параметры для создания линейной диограммы
/// </summary>
public record PdfHistigramInfo
{
/// <summary>
/// Имя файла (включая путь до файла)
/// </summary>
public string FilePath { get; init; } = @"C:\pdfTable.pdf";
/// <summary>
/// Заголовок документа
/// </summary>
public string DocumentTitle { get; init; } = "Гистограмма";
/// <summary>
/// Заголовок диограммы
/// </summary>
public string HistogramTitle { get; init; } = "Гистограмма";
/// <summary>
/// Расположение легенды
/// </summary>
public PdfLegendPosition LegendPosition { get; init; } = PdfLegendPosition.Bottom;
/// <summary>
/// Значения
/// </summary>
public required IEnumerable<PdfHistogramLineInfo> Values { get; init; }
}
}

View File

@ -0,0 +1,15 @@
namespace Cop.Borovkov.Var3.Models
{
public record PdfHistogramLineInfo
{
/// <summary>
/// Название графика
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Значения
/// </summary>
public required IEnumerable<ChartPoint> Points { get; init; }
}
}

View File

@ -0,0 +1,23 @@
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Параметры для создания таблиц в пдф
/// </summary>
public record PdfTableInfo
{
/// <summary>
/// имя файла (включая путь до файла)
/// </summary>
public string FilePath { get; init; } = @"C:\pdfTable.pdf";
/// <summary>
/// название документа(заголовок в документе)
/// </summary>
public string Title { get; init; } = "Таблица";
/// <summary>
/// Список таблиц
/// </summary>
public IEnumerable<string[,]> Tables { get; init; } = [];
}
}

View File

@ -0,0 +1,33 @@
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Параметры для создания таблици в пдф с группировкой по 1 столбцу
/// </summary>
public class PdfTableWithGroupingInfo<TType> where TType : class
{
/// <summary>
/// имя файла (включая путь до файла)
/// </summary>
public string FilePath { get; init; } = @"C:\pdfTable.pdf";
/// <summary>
/// название документа(заголовок в документе)
/// </summary>
public string Title { get; init; } = "Таблица";
/// <summary>
/// Высота заголовков
/// </summary>
public float HeaderHeight { get; init; } = 0.5f;
/// <summary>
/// Параметры столбцов
/// </summary>
public IEnumerable<ColumnInfo> Columns { get; init; } = [];
/// <summary>
/// Список таблиц
/// </summary>
public IEnumerable<RowInfo<TType>> Rows { get; init; } = [];
}
}

View File

@ -0,0 +1,19 @@
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Информация о строке
/// </summary>
/// <typeparam name="T"></typeparam>
public record RowInfo<T> where T : class
{
/// <summary>
/// Высота строки
/// </summary>
public float Height { get; set; } = 0.5f;
/// <summary>
/// Значение строки
/// </summary>
public T Value { get; set; } = null!;
}
}

View File

@ -0,0 +1,34 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using Lab3.Database.Context.Configurations;
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
#nullable disable
namespace Lab3.Database.Context;
public partial class COPContext : DbContext
{
public COPContext(DbContextOptions<COPContext> options)
: base(options)
{
}
public virtual DbSet<EducationForm> EducationForms { get; set; }
public virtual DbSet<Student> Students { get; set; }
public virtual DbSet<StudentSession> StudentSessions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new Configurations.EducationFormConfiguration());
modelBuilder.ApplyConfiguration(new Configurations.StudentConfiguration());
modelBuilder.ApplyConfiguration(new Configurations.StudentSessionConfiguration());
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@ -0,0 +1,46 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
namespace Lab3.Database.Context
{
public partial class COPContext
{
private ICOPContextFunctions _procedures;
public virtual ICOPContextFunctions Functions
{
get
{
if (_procedures is null) _procedures = new COPContextFunctions(this);
return _procedures;
}
set
{
_procedures = value;
}
}
public ICOPContextFunctions GetFunctions()
{
return Functions;
}
}
public partial class COPContextFunctions : ICOPContextFunctions
{
private readonly COPContext _context;
public COPContextFunctions(COPContext context)
{
_context = context;
}
}
}

View File

@ -0,0 +1,31 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using Lab3.Database.Context;
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
namespace Lab3.Database.Context.Configurations
{
public partial class EducationFormConfiguration : IEntityTypeConfiguration<EducationForm>
{
public void Configure(EntityTypeBuilder<EducationForm> entity)
{
entity.HasKey(e => e.Id).HasName("EducationForm_pkey");
entity.ToTable("EducationForm");
entity.HasIndex(e => e.Name, "EducationForm_Name_Name1_key").IsUnique();
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("character varying");
OnConfigurePartial(entity);
}
partial void OnConfigurePartial(EntityTypeBuilder<EducationForm> entity);
}
}

View File

@ -0,0 +1,33 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using Lab3.Database.Context;
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
namespace Lab3.Database.Context.Configurations
{
public partial class StudentConfiguration : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> entity)
{
entity.HasKey(e => e.Id).HasName("Student_pkey");
entity.ToTable("Student");
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.EducationForm)
.IsRequired()
.HasColumnType("character varying");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("character varying");
entity.Property(e => e.StartEducation).HasColumnType("timestamp without time zone");
OnConfigurePartial(entity);
}
partial void OnConfigurePartial(EntityTypeBuilder<Student> entity);
}
}

View File

@ -0,0 +1,30 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using Lab3.Database.Context;
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
namespace Lab3.Database.Context.Configurations
{
public partial class StudentSessionConfiguration : IEntityTypeConfiguration<StudentSession>
{
public void Configure(EntityTypeBuilder<StudentSession> entity)
{
entity.HasKey(e => e.Id).HasName("StudentSession_pkey");
entity.ToTable("StudentSession");
entity.Property(e => e.Id).ValueGeneratedNever();
entity.HasOne(d => d.Student).WithMany(p => p.StudentSessions)
.HasForeignKey(d => d.StudentId)
.HasConstraintName("StudentSession");
OnConfigurePartial(entity);
}
partial void OnConfigurePartial(EntityTypeBuilder<StudentSession> entity);
}
}

View File

@ -0,0 +1,62 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Lab3.Database.Context
{
public static class DbContextExtensions
{
public static async Task<List<T>> SqlQueryAsync<T>(this DbContext db, string sql, object[] parameters = null, CancellationToken cancellationToken = default)
where T : class
{
if (parameters is null)
{
parameters = new object[] { };
}
if (typeof(T).GetProperties().Any())
{
return await db.Database
.SqlQueryRaw<T>(sql, parameters)
.ToListAsync(cancellationToken);
}
else
{
await db.Database.ExecuteSqlRawAsync(sql, parameters, cancellationToken);
return default;
}
}
}
public class OutputParameter<TValue>
{
private bool _valueSet = false;
public TValue _value;
public TValue Value
{
get
{
if (!_valueSet)
throw new InvalidOperationException("Value not set.");
return _value;
}
}
internal void SetValue(object value)
{
_valueSet = true;
_value = null == value || Convert.IsDBNull(value) ? default(TValue) : (TValue)value;
}
}
}

View File

@ -0,0 +1,17 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using Lab3.Database.Models;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
namespace Lab3.Database.Context
{
public partial interface ICOPContextFunctions
{
}
}

View File

@ -0,0 +1,15 @@
namespace Lab3.Database.DTO
{
public record StudentDTO
{
public Guid Id { get; init; }
public string Name { get; init; } = null!;
public DateTime StartEducation { get; init; }
public string EducationForm { get; init; } = null!;
public List<StudentSessionDTO> StudentSessions { get; init; } = [];
}
}

View File

@ -0,0 +1,9 @@
namespace Lab3.Database.DTO
{
public record StudentSessionDTO
{
public decimal Score { get; init; }
public int Number { get; init; }
}
}

View File

@ -0,0 +1,49 @@
using Lab3.Database.Context;
using Lab3.Database.MappingProfiles;
using Lab3.Database.Repository.Implementations;
using Lab3.Database.Repository.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Lab3.Database.Extensions
{
public static class DiExtension
{
public static IServiceCollection AddDatabase(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContextPool<COPContext>(
dbContextOptions =>
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
_ = dbContextOptions.UseNpgsql(configuration.GetConnectionString("COPDataBase"));
_ = dbContextOptions.ConfigureWarnings(warnings => { });
}
);
return services;
}
public static IServiceCollection AddDbMapping(
this IServiceCollection services)
{
services.AddAutoMapper(typeof(StudentMappingProfile));
services.AddAutoMapper(typeof(SessionMappingProfile));
return services;
}
public static IServiceCollection AddRepositories(
this IServiceCollection services)
{
services.AddScoped<IStudentRepository, StudentRepository>();
services.AddScoped<IEducationFormRepository, EducationFormRepository>();
return services;
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
using AutoMapper;
using Lab3.Database.DTO;
using Lab3.Database.Models;
namespace Lab3.Database.MappingProfiles
{
public class SessionMappingProfile : Profile
{
public SessionMappingProfile()
{
_ = CreateMap<StudentSession, StudentSessionDTO>();
_ = CreateMap<StudentSessionDTO, StudentSession>()
.ForMember(s => s.Id, opt => opt.MapFrom(s => Guid.NewGuid()));
}
}
}

View File

@ -0,0 +1,15 @@
using AutoMapper;
using Lab3.Database.DTO;
using Lab3.Database.Models;
namespace Lab3.Database.MappingProfiles
{
public class StudentMappingProfile : Profile
{
public StudentMappingProfile()
{
_ = CreateMap<Student, StudentDTO>();
_ = CreateMap<StudentDTO, Student>();
}
}
}

View File

@ -0,0 +1,13 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Lab3.Database.Models;
public partial class EducationForm
{
public Guid Id { get; set; }
public string Name { get; set; }
}

View File

@ -0,0 +1,19 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Lab3.Database.Models;
public partial class Student
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime StartEducation { get; set; }
public string EducationForm { get; set; }
public virtual ICollection<StudentSession> StudentSessions { get; set; } = new List<StudentSession>();
}

View File

@ -0,0 +1,19 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Lab3.Database.Models;
public partial class StudentSession
{
public Guid Id { get; set; }
public Guid StudentId { get; set; }
public decimal Score { get; set; }
public int Number { get; set; }
public virtual Student Student { get; set; }
}

View File

@ -0,0 +1,35 @@
using Lab3.Database.Context;
using Lab3.Database.Models;
using Lab3.Database.Repository.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Lab3.Database.Repository.Implementations
{
public class EducationFormRepository : IEducationFormRepository
{
private readonly COPContext _context;
public EducationFormRepository(COPContext context)
{
_context = context;
}
public async Task<IEnumerable<string>> Get()
{
return await _context.EducationForms
.Select(f => f.Name)
.ToListAsync();
}
public async Task Update(IEnumerable<string> educationForms)
{
await _context.EducationForms.ExecuteDeleteAsync();
await _context.EducationForms.AddRangeAsync(educationForms.Select(f => new EducationForm()
{
Id = Guid.NewGuid(),
Name = f,
}));
await _context.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,76 @@
using AutoMapper;
using Lab3.Database.Context;
using Lab3.Database.DTO;
using Lab3.Database.Models;
using Lab3.Database.Repository.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Lab3.Database.Repository.Implementations
{
public class StudentRepository : IStudentRepository
{
private readonly IMapper _mapper;
private readonly COPContext _context;
public StudentRepository(IMapper mapper, COPContext context)
{
_mapper = mapper;
_context = context;
}
public async Task<StudentDTO> CreateAsync(StudentDTO studentDTO)
{
var student = _mapper.Map<Student>(studentDTO);
var result = await _context.Students.AddAsync(student);
await _context.SaveChangesAsync();
return _mapper.Map<StudentDTO>(result.Entity);
}
public Task<StudentDTO> DeleteAsync(Guid id) => throw new NotImplementedException();
public async Task<List<StudentDTO>> GetAsync(int limit = 10000, int offset = 0)
=> _mapper.Map<List<StudentDTO>>(
await _context.Students
.Include(s => s.StudentSessions)
.Skip(offset)
.Take(limit)
.ToListAsync());
public async Task<StudentDTO?> GetAsync(Guid id)
{
return _mapper.Map<StudentDTO>(
await _context.Students
.Include(s => s.StudentSessions)
.FirstOrDefaultAsync());
}
public async Task<StudentDTO?> UpdateAsync(StudentDTO studentDTO)
{
var student = _mapper.Map<Student>(studentDTO);
var currStudent = await _context.Students.FindAsync(student.Id);
if (currStudent == null)
{
return null;
}
currStudent.Name = student.Name;
currStudent.StartEducation = student.StartEducation;
currStudent.EducationForm = student.EducationForm;
foreach (var session in currStudent.StudentSessions)
{
session.Score = student.StudentSessions
.FirstOrDefault(s => s.Number == session.Number)?.Score ?? session.Score;
}
await _context.SaveChangesAsync();
return _mapper.Map<StudentDTO>(currStudent);
}
}
}

View File

@ -0,0 +1,9 @@
namespace Lab3.Database.Repository.Interfaces
{
public interface IEducationFormRepository
{
Task<IEnumerable<string>> Get();
Task Update(IEnumerable<string> educationForms);
}
}

View File

@ -0,0 +1,17 @@
using Lab3.Database.DTO;
namespace Lab3.Database.Repository.Interfaces
{
public interface IStudentRepository
{
Task<List<StudentDTO>> GetAsync(int limit = 10000, int offset = 0);
Task<StudentDTO?> GetAsync(Guid id);
Task<StudentDTO?> UpdateAsync(StudentDTO studentDTO);
Task<StudentDTO> DeleteAsync(Guid id);
Task<StudentDTO> CreateAsync(StudentDTO studentDTO);
}
}

View File

@ -0,0 +1,54 @@
{
"CodeGenerationMode": 4,
"ContextClassName": "COPContext",
"ContextNamespace": "Context",
"FilterSchemas": false,
"IncludeConnectionString": false,
"ModelNamespace": "Models",
"OutputContextPath": "Context",
"OutputPath": "Models",
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "Lab3.Database",
"Schemas": null,
"SelectedHandlebarsLanguage": 2,
"SelectedToBeGenerated": 0,
"T4TemplatePath": null,
"Tables": [
{
"Name": "public.EducationForm",
"ObjectType": 0
},
{
"Name": "public.Student",
"ObjectType": 0
},
{
"Name": "public.StudentSession",
"ObjectType": 0
}
],
"UiHint": null,
"UncountableWords": null,
"UseAsyncStoredProcedureCalls": true,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDateOnlyTimeOnly": true,
"UseDbContextSplitting": true,
"UseDecimalDataAnnotationForSprocResult": true,
"UseFluentApiOnly": true,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": true,
"UseNoNavigations": false,
"UseNoObjectFilter": true,
"UseNodaTime": false,
"UseNullableReferences": false,
"UsePrefixNavigationNaming": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false
}

View File

@ -0,0 +1,48 @@
using DocumentFormat.OpenXml.Office2010.Excel;
using Lab3.Database.Extensions;
using Lab3.Database.Repository.Interfaces;
using Lab3.Forms;
using Lab3.MappingProfiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Lab3.Extensions
{
public static class DIExtension
{
public static IServiceCollection ConfigureDAL(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDatabase(configuration);
services.AddDbMapping();
services.AddRepositories();
return services;
}
public static IServiceCollection AddForms(
this IServiceCollection services)
{
services.AddScoped<MainForm>();
services.AddScoped<CatalogForm>();
services.AddScoped<Func<Guid?, CreateForm>>(sp => (id
=> new CreateForm(
sp.GetRequiredService<IStudentRepository>(),
sp.GetRequiredService<IEducationFormRepository>(),
id
)));
return services;
}
public static IServiceCollection AddMapping(
this IServiceCollection services)
{
services.AddAutoMapper(typeof(StudentViewMappingProfile));
return services;
}
}
}

View File

@ -0,0 +1,76 @@
namespace Lab3.Forms
{
partial class CatalogForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
Catalog = new DataGridView();
EducationForm = new DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)Catalog).BeginInit();
SuspendLayout();
//
// Catalog
//
Catalog.AllowUserToAddRows = false;
Catalog.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
Catalog.Columns.AddRange(new DataGridViewColumn[] { EducationForm });
Catalog.Dock = DockStyle.Fill;
Catalog.Location = new Point(0, 0);
Catalog.Name = "Catalog";
Catalog.RowHeadersWidth = 51;
Catalog.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
Catalog.Size = new Size(800, 450);
Catalog.TabIndex = 0;
Catalog.CellEndEdit += Catalog_CellEndEditAsync;
Catalog.KeyDown += Catalog_KeyDownAsync;
//
// EducationForm
//
EducationForm.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
EducationForm.HeaderText = "Форма обучения";
EducationForm.MinimumWidth = 6;
EducationForm.Name = "EducationForm";
//
// CatalogForm
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(Catalog);
Name = "CatalogForm";
Text = "CatalogForm";
Load += CatalogForm_LoadAsync;
((System.ComponentModel.ISupportInitialize)Catalog).EndInit();
ResumeLayout(false);
}
#endregion
private DataGridView Catalog;
private DataGridViewTextBoxColumn EducationForm;
}
}

View File

@ -0,0 +1,97 @@
using Lab3.Database.Repository.Interfaces;
namespace Lab3.Forms
{
public partial class CatalogForm : Form
{
private readonly IEducationFormRepository _repository;
public CatalogForm(IEducationFormRepository educationFormRepository)
{
_repository = educationFormRepository;
InitializeComponent();
}
private async void CatalogForm_LoadAsync(object sender, EventArgs e)
{
Catalog.Rows.Clear();
var values = (await _repository.Get()).ToList();
for (int i = 0; i < values.Count; i++)
{
Catalog.Rows.Add();
Catalog.Rows[i].Cells[0].Value = values[i];
}
}
private async void Catalog_CellEndEditAsync(object sender, DataGridViewCellEventArgs e)
{
await LoadAsync();
}
private async Task LoadAsync()
{
try
{
List<string> values = new List<string>();
for (int i = 0; i < Catalog.Rows.Count; ++i)
{
string? val = (string?)Catalog.Rows[i].Cells[0].Value;
if (string.IsNullOrEmpty(val))
{
MessageBox.Show(
"Неверные данные",
"Ошибка",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
values.Add(val);
}
await _repository.Update(values);
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Ошибка",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private async void Catalog_KeyDownAsync(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Insert)
{
Catalog.Rows.Add();
}
if (e.KeyCode == Keys.Delete && Catalog.SelectedRows.Count == 1)
{
if (MessageBox.Show(
"Удалить?",
"Удаление",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.Yes)
{
try
{
Catalog.Rows.RemoveAt(Catalog.SelectedRows[0].Index);
await LoadAsync();
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Ошибка",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="EducationForm.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

View File

@ -0,0 +1,317 @@
namespace Lab3.Forms
{
partial class CreateForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
StartEducationDataPicker = new CustomsComponentsVar2.CustomDateTimePicker();
FormSelector = new ComponentsLab.VisualSelectionComponent();
label1 = new Label();
label2 = new Label();
NameTextBox = new TextBox();
label3 = new Label();
session1Score = new NumericUpDown();
label4 = new Label();
label5 = new Label();
label6 = new Label();
session2Score = new NumericUpDown();
label7 = new Label();
session3Score = new NumericUpDown();
label8 = new Label();
session6Score = new NumericUpDown();
label9 = new Label();
session5Score = new NumericUpDown();
label10 = new Label();
session4Score = new NumericUpDown();
ButtonSave = new Button();
buttonCancel = new Button();
((System.ComponentModel.ISupportInitialize)session1Score).BeginInit();
((System.ComponentModel.ISupportInitialize)session2Score).BeginInit();
((System.ComponentModel.ISupportInitialize)session3Score).BeginInit();
((System.ComponentModel.ISupportInitialize)session6Score).BeginInit();
((System.ComponentModel.ISupportInitialize)session5Score).BeginInit();
((System.ComponentModel.ISupportInitialize)session4Score).BeginInit();
SuspendLayout();
//
// StartEducationDataPicker
//
StartEducationDataPicker.Location = new Point(430, 32);
StartEducationDataPicker.Name = "StartEducationDataPicker";
StartEducationDataPicker.Size = new Size(456, 55);
StartEducationDataPicker.TabIndex = 0;
StartEducationDataPicker.Value = new DateTime(2024, 11, 6, 22, 54, 54, 353);
//
// FormSelector
//
FormSelector.BorderStyle = BorderStyle.FixedSingle;
FormSelector.ComboBoxSelectedValue = "";
FormSelector.Location = new Point(436, 133);
FormSelector.Margin = new Padding(3, 4, 3, 4);
FormSelector.Name = "FormSelector";
FormSelector.Size = new Size(352, 31);
FormSelector.TabIndex = 1;
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(430, 9);
label1.Name = "label1";
label1.Size = new Size(134, 20);
label1.TabIndex = 2;
label1.Text = "Дата поступления";
//
// label2
//
label2.AutoSize = true;
label2.Location = new Point(436, 101);
label2.Name = "label2";
label2.Size = new Size(128, 20);
label2.TabIndex = 3;
label2.Text = "Форма обучения";
//
// NameTextBox
//
NameTextBox.Location = new Point(12, 60);
NameTextBox.Name = "NameTextBox";
NameTextBox.Size = new Size(315, 27);
NameTextBox.TabIndex = 4;
//
// label3
//
label3.AutoSize = true;
label3.Location = new Point(12, 18);
label3.Name = "label3";
label3.Size = new Size(42, 20);
label3.TabIndex = 5;
label3.Text = "ФИО";
//
// session1Score
//
session1Score.DecimalPlaces = 2;
session1Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session1Score.Location = new Point(12, 156);
session1Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session1Score.Name = "session1Score";
session1Score.Size = new Size(150, 27);
session1Score.TabIndex = 6;
//
// label4
//
label4.AutoSize = true;
label4.Location = new Point(12, 101);
label4.Name = "label4";
label4.Size = new Size(107, 20);
label4.TabIndex = 7;
label4.Text = "Успеваемость";
//
// label5
//
label5.AutoSize = true;
label5.Location = new Point(12, 133);
label5.Name = "label5";
label5.Size = new Size(65, 20);
label5.TabIndex = 8;
label5.Text = "Сессия1";
//
// label6
//
label6.AutoSize = true;
label6.Location = new Point(12, 188);
label6.Name = "label6";
label6.Size = new Size(65, 20);
label6.TabIndex = 10;
label6.Text = "Сессия2";
//
// session2Score
//
session2Score.DecimalPlaces = 2;
session2Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session2Score.Location = new Point(12, 211);
session2Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session2Score.Name = "session2Score";
session2Score.Size = new Size(150, 27);
session2Score.TabIndex = 9;
//
// label7
//
label7.AutoSize = true;
label7.Location = new Point(12, 247);
label7.Name = "label7";
label7.Size = new Size(65, 20);
label7.TabIndex = 12;
label7.Text = "Сессия3";
//
// session3Score
//
session3Score.DecimalPlaces = 2;
session3Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session3Score.Location = new Point(12, 270);
session3Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session3Score.Name = "session3Score";
session3Score.Size = new Size(150, 27);
session3Score.TabIndex = 11;
//
// label8
//
label8.AutoSize = true;
label8.Location = new Point(177, 247);
label8.Name = "label8";
label8.Size = new Size(65, 20);
label8.TabIndex = 18;
label8.Text = "Сессия6";
//
// session6Score
//
session6Score.DecimalPlaces = 2;
session6Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session6Score.Location = new Point(177, 270);
session6Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session6Score.Name = "session6Score";
session6Score.Size = new Size(150, 27);
session6Score.TabIndex = 17;
//
// label9
//
label9.AutoSize = true;
label9.Location = new Point(177, 188);
label9.Name = "label9";
label9.Size = new Size(65, 20);
label9.TabIndex = 16;
label9.Text = "Сессия5";
//
// session5Score
//
session5Score.DecimalPlaces = 2;
session5Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session5Score.Location = new Point(177, 211);
session5Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session5Score.Name = "session5Score";
session5Score.Size = new Size(150, 27);
session5Score.TabIndex = 15;
//
// label10
//
label10.AutoSize = true;
label10.Location = new Point(177, 133);
label10.Name = "label10";
label10.Size = new Size(65, 20);
label10.TabIndex = 14;
label10.Text = "Сессия4";
//
// session4Score
//
session4Score.DecimalPlaces = 2;
session4Score.Increment = new decimal(new int[] { 1, 0, 0, 131072 });
session4Score.Location = new Point(177, 156);
session4Score.Maximum = new decimal(new int[] { 5, 0, 0, 0 });
session4Score.Name = "session4Score";
session4Score.Size = new Size(150, 27);
session4Score.TabIndex = 13;
//
// ButtonSave
//
ButtonSave.Location = new Point(430, 258);
ButtonSave.Name = "ButtonSave";
ButtonSave.Size = new Size(164, 39);
ButtonSave.TabIndex = 19;
ButtonSave.Text = "Сохранить";
ButtonSave.UseVisualStyleBackColor = true;
ButtonSave.Click += ButtonSave_ClickAsync;
//
// buttonCancel
//
buttonCancel.Location = new Point(624, 258);
buttonCancel.Name = "buttonCancel";
buttonCancel.Size = new Size(164, 39);
buttonCancel.TabIndex = 20;
buttonCancel.Text = "Отмена";
buttonCancel.UseVisualStyleBackColor = true;
//
// CreateForm
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 309);
Controls.Add(buttonCancel);
Controls.Add(ButtonSave);
Controls.Add(label8);
Controls.Add(session6Score);
Controls.Add(label9);
Controls.Add(session5Score);
Controls.Add(label10);
Controls.Add(session4Score);
Controls.Add(label7);
Controls.Add(session3Score);
Controls.Add(label6);
Controls.Add(session2Score);
Controls.Add(label5);
Controls.Add(label4);
Controls.Add(session1Score);
Controls.Add(label3);
Controls.Add(NameTextBox);
Controls.Add(label2);
Controls.Add(label1);
Controls.Add(StartEducationDataPicker);
Controls.Add(FormSelector);
Name = "CreateForm";
Text = "CreateForm";
Load += CreateForm_LoadAsync;
((System.ComponentModel.ISupportInitialize)session1Score).EndInit();
((System.ComponentModel.ISupportInitialize)session2Score).EndInit();
((System.ComponentModel.ISupportInitialize)session3Score).EndInit();
((System.ComponentModel.ISupportInitialize)session6Score).EndInit();
((System.ComponentModel.ISupportInitialize)session5Score).EndInit();
((System.ComponentModel.ISupportInitialize)session4Score).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private CustomsComponentsVar2.CustomDateTimePicker StartEducationDataPicker;
private ComponentsLab.VisualSelectionComponent FormSelector;
private Label label1;
private Label label2;
private TextBox NameTextBox;
private Label label3;
private NumericUpDown session1Score;
private Label label4;
private Label label5;
private Label label6;
private NumericUpDown session2Score;
private Label label7;
private NumericUpDown session3Score;
private Label label8;
private NumericUpDown session6Score;
private Label label9;
private NumericUpDown session5Score;
private Label label10;
private NumericUpDown session4Score;
private Button ButtonSave;
private Button buttonCancel;
}
}

View File

@ -0,0 +1,110 @@
using Lab3.Database.DTO;
using Lab3.Database.Models;
using Lab3.Database.Repository.Interfaces;
namespace Lab3.Forms
{
public partial class CreateForm : Form
{
private readonly IStudentRepository _studentRepository;
private readonly IEducationFormRepository _educationFormRepository;
private readonly Guid? _updatedStudentGuid = null;
public CreateForm(
IStudentRepository studentRepository,
IEducationFormRepository educationFormRepository,
Guid? updatedStudentGuid)
{
_studentRepository = studentRepository;
_educationFormRepository = educationFormRepository;
_updatedStudentGuid = updatedStudentGuid;
InitializeComponent();
}
private async void CreateForm_LoadAsync(object sender, EventArgs e)
{
FormSelector.Fill(await _educationFormRepository.Get());
StartEducationDataPicker.DateStart = DateTime.Now.AddYears(-6);
StartEducationDataPicker.DateEnd = DateTime.Now;
if (_updatedStudentGuid == null)
{
return;
}
var student = await _studentRepository.GetAsync(_updatedStudentGuid.Value);
NameTextBox.Text = student.Name;
StartEducationDataPicker.Value = student.StartEducation;
FormSelector.ComboBoxSelectedValue = student.EducationForm;
session1Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 1)?.Score ?? decimal.Zero;
session2Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 2)?.Score ?? decimal.Zero;
session3Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 3)?.Score ?? decimal.Zero;
session4Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 4)?.Score ?? decimal.Zero;
session5Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 5)?.Score ?? decimal.Zero;
session6Score.Value = student.StudentSessions
.FirstOrDefault(s => s.Number == 6)?.Score ?? decimal.Zero;
}
private async void ButtonSave_ClickAsync(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(NameTextBox.Text)
|| string.IsNullOrEmpty(FormSelector.ComboBoxSelectedValue))
{
throw new Exception();
}
StudentDTO student = new()
{
Id = _updatedStudentGuid ?? Guid.NewGuid(),
Name = NameTextBox.Text,
StartEducation = StartEducationDataPicker.Value,
EducationForm = FormSelector.ComboBoxSelectedValue,
StudentSessions = [
new(){
Score = session1Score.Value,
Number = 1,
},
new(){
Score = session2Score.Value,
Number = 2,
},
new(){
Score = session3Score.Value,
Number = 3,
},
new(){
Score = session4Score.Value,
Number = 4,
},
new(){
Score = session5Score.Value,
Number = 5,
},
new(){
Score = session6Score.Value,
Number = 6,
},
],
};
if (_updatedStudentGuid != null)
{
await _studentRepository.UpdateAsync(student);
}
else
{
await _studentRepository.CreateAsync(student);
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,60 @@
namespace Lab3.Forms
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
StudentsListBox = new Cop.Borovkov.Var3.Components.CustomListBox();
SuspendLayout();
//
// StudentsListBox
//
StudentsListBox.Dock = DockStyle.Fill;
StudentsListBox.Location = new Point(0, 0);
StudentsListBox.Name = "StudentsListBox";
StudentsListBox.Selected = "";
StudentsListBox.Size = new Size(800, 450);
StudentsListBox.TabIndex = 0;
//
// MainForm
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(StudentsListBox);
Name = "MainForm";
Text = "MainForm";
Load += MainForm_LoadAsync;
KeyDown += MainForm_KeyDown;
ResumeLayout(false);
}
#endregion
private Cop.Borovkov.Var3.Components.CustomListBox StudentsListBox;
}
}

View File

@ -0,0 +1,64 @@
using AutoMapper;
using Lab3.Database.Repository.Interfaces;
using Lab3.Models;
namespace Lab3.Forms
{
public partial class MainForm : Form
{
private readonly IStudentRepository _studentRepository;
private readonly IMapper _mapper;
private readonly Func<Guid?, CreateForm> _getCreateOrUpdateForm;
public MainForm(
IStudentRepository repository,
IMapper mapper,
Func<Guid?, CreateForm> getCreateOrUpdateForm)
{
_studentRepository = repository;
_mapper = mapper;
_getCreateOrUpdateForm = getCreateOrUpdateForm;
InitializeComponent();
}
private async void MainForm_LoadAsync(object sender, EventArgs e)
{
var students = _mapper.Map<List<StudentViewModel>>(await _studentRepository.GetAsync());
StudentsListBox.FillValues(
students.Select(s => string.Join(" ",
[
s.Id,
s.Name,
s.EducationForm,
s.StartEducation.ToLongDateString(),
s.SessionMarks,
]
))
);
}
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers != Keys.Control)
{
return;
}
switch (e.KeyCode)
{
case Keys.A:
_getCreateOrUpdateForm(null).Show(this);
break;
case Keys.U:
_getCreateOrUpdateForm(
Guid.Parse(StudentsListBox.Selected.Split()[0])
).Show(this);
break;
default:
return;
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Folder Include="Logic\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ComponentsLibraryLab1" Version="8.0.2" />
<PackageReference Include="ComponentsLibraryLab2" Version="8.0.1" />
<PackageReference Include="Cop.Borovkov.Var3" Version="8.0.1" />
<PackageReference Include="CustomComponentsVar2" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lab3.Database\Lab3.Database.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
using AutoMapper;
using Lab3.Database.DTO;
using Lab3.Models;
namespace Lab3.MappingProfiles
{
public class StudentViewMappingProfile : Profile
{
public StudentViewMappingProfile()
{
_ = CreateMap<StudentDTO, StudentViewModel>()
;
}
}
}

View File

@ -0,0 +1,12 @@
using Lab3.Database.DTO;
using System.Globalization;
namespace Lab3.Models
{
public record StudentViewModel : StudentDTO
{
public string SessionMarks => string.Join("; ", StudentSessions
.OrderBy(s => s.Number)
.Select(s => $"сессия{s.Number:d}: {s.Score:n2}"));
}
}

View File

@ -0,0 +1,41 @@
using Lab3.Extensions;
using Lab3.Forms;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Lab3
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
var app = CreateHostBuilder().Build();
Application.Run(app.Services.GetRequiredService<CatalogForm>());
}
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureAppConfiguration(c
=> c.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true))
.ConfigureServices((context, services) => {
services.ConfigureDAL(context.Configuration);
services.AddMapping();
services.AddForms();
});
}
}
}

View File

@ -0,0 +1,5 @@
{
"ConnectionStrings": {
"COPDataBase": "Host=localhost;Username=postgres;Password=postgres;Database=COP"
}
}

View File

@ -0,0 +1,32 @@
using MigraDoc.DocumentObjectModel;
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Тип выравнивания элементов
/// </summary>
public enum PdfAlignmentType
{
/// <summary>
/// По центру
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Center)]
Center,
/// <summary>
/// По левому краю
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Left)]
Left,
/// <summary>
/// По правому краю
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Right)]
Rigth
}
}

View File

@ -0,0 +1,25 @@
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Варианты расположения легенды
/// </summary>
public enum PdfLegendPosition
{
/// <summary>
/// Снизу(по умолчанию)
/// </summary>
Bottom,
/// <summary>
/// Справа
/// </summary>
Right,
/// <summary>
/// Слева
/// </summary>
Left,
/// <summary>
/// Сверху
/// </summary>
Top
}
}

View File

@ -0,0 +1,36 @@
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Типы отступов после элементов (вертикальный)
/// </summary>
public enum PdfMargin
{
/// <summary>
/// Отступа нет
/// </summary>
[TypeValue<string>("ocm")]
None,
/// <summary>
/// Отступ небольшой
/// </summary>
[TypeValue<string>("0.3cm")]
Smal,
/// <summary>
/// Отступ средний
/// </summary>
[TypeValue<string>("1cm")]
Medium,
/// <summary>
/// Отступ большой
/// </summary>
[TypeValue<string>("1.6cm")]
Big
}
}

View File

@ -0,0 +1,54 @@
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Типы стилей в документе
/// </summary>
public enum PdfStyleType
{
/// <summary>
/// Основная часть документа
/// </summary>
[TypeValue<string>("Normal")]
Basic,
/// <summary>
/// Название документа
/// </summary>
[TypeValue<string>("NormalTitle")]
Title,
/// <summary>
/// Уровень в списке 1
/// </summary>
[TypeValue<string>("BulletList")]
[TypeValue<int>(1)]
ListLevel1,
/// <summary>
/// Уровень в списке 2
/// </summary>
[TypeValue<string>("BulletList2")]
[TypeValue<int>(2)]
ListLevel2,
/// <summary>
/// Уровень в списке 3
/// </summary>
[TypeValue<string>("BulletList3")]
[TypeValue<int>(3)]
ListLevel3,
/// <summary>
/// Жирный шрифт
/// </summary>
[TypeValue<string>("NormalBold")]
Bold,
/// <summary>
/// Мелкий шрифт
/// </summary>
[TypeValue<string>("NormalSmall")]
Small
}
}

View File

@ -0,0 +1,15 @@
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// </summary>
public interface IPdfColumnItem
{
/// <summary>
/// </summary>
public float? Size { get; }
/// <summary>
/// </summary>
public string? Title { get; set; }
}
}

View File

@ -0,0 +1,64 @@
using PIHelperSh.PdfCreator.Models.ImageModels;
using PIHelperSh.PdfCreator.Models.PieChartModel;
using PIHelperSh.PdfCreator.Models.TableModels;
using PIHelperSh.PdfCreator.Models.TextModels;
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// Создатель pdf документа
/// </summary>
public interface IPdfCreator
{
/// <summary>
/// Создаём параграф
/// </summary>
/// <param name="paragraph">Модель параграфа, который вставляем</param>
public void AddParagraph(PdfParagraph paragraph);
/// <summary>
/// Создаём маркированный список
/// </summary>
/// <param name="List">Модель списка(может быть многоуровневым). Max - 3 уровня</param>
public void AddList(PdfList List);
/// <summary>
/// Создаёт таблицу, с шапкой из 2-х строк(с группировками)
/// </summary>
/// <typeparam name="T">Тип DTO, из которой берутся данные в таблицу</typeparam>
/// <param name="header">Модель самой таблицы</param>
/// <param name="rowHeaded"></param>
public void AddTable<T>(PdfTable<T> header, bool rowHeaded = false);
/// <summary>
/// Создаёт табличку, наподобие той, что с T, но проще
/// </summary>
/// <param name="tableData"></param>
public void AddSimpleTable(PDFSimpleTable tableData);
/// <summary>
/// Создаёт круговую диаграмму.
/// </summary>
/// <param name="pieChart">Модель для круговой диаграммы</param>
public void AddChart(PdfPieChartModel pieChart);
/// <summary>
/// Добавляем на лист изображение. Можно по пути, можно по потоку, можно по Base64 строке
/// </summary>
/// <param name="image">Модель одного изображения</param>
public void AddImage(PdfImage image);
/// <summary>
/// Метод сохранения созданного PDF документа
/// </summary>
/// <returns>Поток MemoryStream с документом</returns>
public MemoryStream SavePdf();
/// <summary>
/// Метод сохранения созданного PDF документа в файл
/// </summary>
/// <param name="filename">Имя файла и путь до него. Проверки на расширение нет</param>
public void SavePdf(string filename);
}
}

View File

@ -0,0 +1,20 @@
using PIHelperSh.PdfCreator.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// Аналогично word этот интерфейс нужен для работы списков.
/// </summary>
public interface IPdfElement
{
/// <summary>
/// //Отступ после(может быть и у списков и у параграфов)
/// </summary>
PdfMargin MarginAfter { get; }
}
}

View File

@ -0,0 +1,75 @@
namespace PIHelperSh.PdfCreator.Models.ImageModels
{
/// <summary>
/// Удобный метод для хранения изображений (для создания Pdf документа можно использовать либо путь до фото, либо поток с данными о нём)
/// </summary>
public class MiraDocImage
{
/// <summary>
/// Путь до фото
/// </summary>
public string? Path { get; set; } = null;
/// <summary>
/// Поток фото
/// </summary>
public Stream? Source
{
get => _source;
set
{
_source = value;
_b64Source = null;
}
}
private Stream? _source;
private string? _b64Source = null;
/// <summary>
/// Создание изображения из пути
/// </summary>
/// <param name="path">Путь</param>
/// <returns></returns>
public static MiraDocImage CreateFromPath(string path) => new() { Path = path };
/// <summary>
/// Создания изображения из строки base64
/// </summary>
/// <param name="base64">изображение в формате base64</param>
/// <returns></returns>
public static MiraDocImage CreateFromBase64(string base64) => new() { _b64Source = $"base64:{base64}" };
/// <summary>
/// Создание изображения из потока
/// </summary>
/// <param name="stream">Поток</param>
/// <returns></returns>
public static MiraDocImage CreateFromStream(Stream stream) => new() { Source = stream };
/// <summary>
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public string GetImageForMiraDoc()
{
if (!string.IsNullOrEmpty(Path)) return Path;
if (_b64Source != null) return _b64Source;
if (_source == null) throw new ArgumentNullException("Для изображения не задан ни путь, ни поток. Необходимо заполнить как минимум одно из этого, для работы");
_b64Source = GetImageAsStringBase64(_source);
return _b64Source;
}
private string GetImageAsStringBase64(Stream stream)
{
//Вот это вот, в общем-то, ещё один жуткий костыль. Дело в том, что MigraDoc не поддерживает создание изображений из потоков. Только из путей. Но всё же, способ есть
//Вот он собственно. И да, фактически это жуткая система, записывающая картинку в строку. Увы, по другому не получится
stream.Position = 0;
int count = (int)stream.Length;
byte[] data = new byte[count];
stream.Read(data, 0, count);
return $"base64:{Convert.ToBase64String(data)}";
}
}
}

View File

@ -0,0 +1,41 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.ImageModels
{
/// <summary>
/// Информация о изображении
/// </summary>
public class PdfImage : IPdfElement
{
/// <summary>
/// Путь до изображения
/// </summary>
public MiraDocImage Image { get; set; } = null!;
/// <summary>
/// Ширина изображения
/// </summary>
public int? Width { get; set; } = null;
/// <summary>
/// Высота изображения
/// </summary>
public int? Height { get; set; } = null;
/// <summary>
/// Выравнивание текста внутри параграфа (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType ImageAlignment { get; set; } = PdfAlignmentType.Left;
/// <summary>
/// Отступ после параграфа (по умолчанию средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,42 @@
using System.Drawing;
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Элемент данных. Одно значение для диаграммы.
/// </summary>
public class PdfHistogramData
{
/// <summary>
/// Название варианта
/// </summary>
public string DisplayName { get; set; } = null!;
/// <summary>
/// Значение варианта(по ним строят диаграмму)
/// </summary>
public IEnumerable<(string Name, double Value)> Value { get; set; }
/// <summary>
/// Цвет области на диаграме. При null будет использоватсся выдача цветов по умолчанию)
/// </summary>
public Color? Color { get; set; } = null;
/// <summary>
/// </summary>
public PdfHistogramData()
{
Value = [];
}
/// <summary>
/// </summary>
/// <param name="displayName"></param>
/// <param name="value"></param>
public PdfHistogramData(string displayName, IEnumerable<(string, double)> value)
{
DisplayName = displayName;
Value = value;
}
}
}

View File

@ -0,0 +1,13 @@
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Модель линейной диограммы
/// </summary>
public class PdfHistogramModel : PdfPieChartModel
{
/// <summary>
/// Набор данных для создания диаграммы
/// </summary>
public new List<PdfHistogramData> DataSet { get; set; } = [];
}
}

View File

@ -0,0 +1,39 @@
using System.Drawing;
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Элемент данных. Одно значение для диаграммы.
/// </summary>
public class PdfPieChartData
{
/// <summary>
/// Название варианта
/// </summary>
public string DisplayName { get; set; } = null!;
/// <summary>
/// Значение варианта(по ним строят диаграмму)
/// </summary>
public double Value { get; set; }
/// <summary>
/// Цвет области на диаграме. При null будет использоватсся выдача цветов по умолчанию)
/// </summary>
public Color? Color { get; set; } = null;
/// <summary>
/// </summary>
public PdfPieChartData() { }
/// <summary>
/// </summary>
/// <param name="displayName"></param>
/// <param name="value"></param>
public PdfPieChartData(string displayName, double value)
{
DisplayName = displayName;
Value = value;
}
}
}

View File

@ -0,0 +1,45 @@
using PIHelperSh.PdfCreator.Enums;
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Модель круговой диаграммы
/// </summary>
public class PdfPieChartModel
{
/// <summary>
/// Ширина диаграммы в см по умолчанию на всю ширину
/// </summary>
public double Width { get; set; } = 16;
/// <summary>
/// Высота диаграммы в см
/// </summary>
public double Height { get; set; } = 12;
/// <summary>
/// Заголовок диаграммы. Будет отображён над ней
/// </summary>
public string ChartName { get; set; } = null!;
/// <summary>
/// Стиль заголовка
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание заголовка по горизонтали
/// </summary>
public PdfAlignmentType HeaderAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Вариант расположения легенды
/// </summary>
public PdfLegendPosition LegendPosition { get; set; } = PdfLegendPosition.Bottom;
/// <summary>
/// Набор данных для создания диаграммы
/// </summary>
public List<PdfPieChartData> DataSet { get; set; } = new();
}
}

View File

@ -0,0 +1,51 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Простая табличка в PDF
/// </summary>
public class PDFSimpleTable
{
/// <summary>
/// Заголовок
/// </summary>
public List<IPdfColumnItem>? Header { get; set; } = null;
/// <summary>
/// Стиль заголовка (по умолчанию - жирный)
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание текста внутри заголовка (по умолчанию - по центру)
/// </summary>
public PdfAlignmentType HeaderHorizontalAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Набор строк
/// </summary>
public List<PDFSimpleTableRow> Rows { get; set; } = new();
/// <summary>
/// Базовый стиль строк
/// </summary>
public PdfStyleType RowStyle = PdfStyleType.Basic;
/// <summary>
/// Базовое выравнивание элементов сторок
/// </summary>
public PdfAlignmentType RowHorizontalAlignment = PdfAlignmentType.Rigth;
/// <summary>
/// Отступ после таблицы
/// </summary>
public PdfMargin MarginAfter = PdfMargin.None;
/// <summary>
/// Меняем ли ориентацию страницы на альбомную
/// </summary>
public bool ChangePageOrientation = false;
}
}

View File

@ -0,0 +1,30 @@
using PIHelperSh.PdfCreator.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Строка простой таблицы
/// </summary>
public class PDFSimpleTableRow
{
/// <summary>
/// Элемменты данной стоки
/// </summary>
public List<string> Items = new List<string>();
/// <summary>
/// Стиль элементов строки, если он отличается от стиля строк, определённого в таблице
/// </summary>
public PdfStyleType? Style = null;
/// <summary>
/// Выравнивание элементов строки, если оно отличается от выравнивания, определённого в таблице
/// </summary>
public PdfAlignmentType? Alignment = null;
}
}

View File

@ -0,0 +1,47 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Таблица для вставки на лист
/// </summary>
/// <typeparam name="T"></typeparam>
public class PdfTable<T>
{
/// <summary>
/// Шапка таблицы(2 строки/2 столбца)
/// </summary>
public List<IPdfColumnItem>? Header { get; set; } = null;
/// <summary>
/// Стиль заголовка (по умолчанию - жирный)
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание текста внутри заголовка (по умолчанию - по центру)
/// </summary>
public PdfAlignmentType HeaderHorizontalAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Список объектов, информация о которых будет в таблице.
/// </summary>
public List<(T value, float height)> Records { get; set; } = new();
/// <summary>
/// Стиль объектов в таблице (по умолчанию - базовый)
/// </summary>
public PdfStyleType RecordStyle { get; set; } = PdfStyleType.Basic;
/// <summary>
/// Выравнивание текста объектов в таблице (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType RecordHorizontalAlignment { get; set; } = PdfAlignmentType.Left;
/// <summary>
/// Высота заголовка
/// </summary>
public float HeaderHeaight { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Столбец(строка) таблицы, определяющий дальнейшее содержимое
/// </summary>
public class PdfTableColumn : IPdfColumnItem
{
/// <summary>
/// Заголовок
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Размер (Ширина столбца/Высота строки)
/// </summary>
public float? Size { get; set; }
/// <summary>
/// Название свойства, которое будет заполнять столбец
/// </summary>
public string PropertyName { get; set; } = null!;
}
}

View File

@ -0,0 +1,25 @@
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Группа столбцов(строк) таблицы, определяющий дальнейшее содержимое
/// </summary>
public class PdfTableColumnGroup : IPdfColumnItem
{
/// <summary>
/// Заголовок
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Количество элементов в группе
/// </summary>
public float? Size => InnerColumns.Sum(x => x.Size);
/// <summary>
/// Столбцы/строки в группе
/// </summary>
public List<PdfTableColumn> InnerColumns { get; set; } = new();
}
}

View File

@ -0,0 +1,26 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TextModels
{
/// <summary>
/// Список элементов в PDF
/// </summary>
public class PdfList : IPdfElement
{
/// <summary>
/// Элементы списка (параграфы или иные спсики)
/// </summary>
public List<IPdfElement> Content { get; set; } = new();
/// <summary>
/// Отступ после списка (по умолчанию - средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,36 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TextModels
{
/// <summary>
/// Параграф в PDF
/// </summary>
public class PdfParagraph : IPdfElement
{
/// <summary>
/// Текст параграфа
/// </summary>
public string Text { get; set; } = string.Empty;
/// <summary>
/// Стиль параграфа (по умолчанию - базовый)
/// </summary>
public PdfStyleType Style { get; set; } = PdfStyleType.Basic;
/// <summary>
/// Выравнивание текста внутри параграфа (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType ParagraphAlignment { get; set; } = PdfAlignmentType.Left;
/// <summary>
/// Отступ после параграфа (по умолчанию средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Title>Библиотека PDF</Title>
<Authors>MaximK</Authors>
<Description>Небольшая надстройка для более удобной работы с PDF</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/KuzarinM/PIHelperSh/tree/master/PIHelperSh.PdfCreater</RepositoryUrl>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.1.1</Version>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="PdfSharp.MigraDoc.Standard" Version="1.51.15" />
<PackageReference Include="PIHelperSh.Core" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,602 @@
using MigraDoc.DocumentObjectModel;
using System.Text;
using MigraDoc.Rendering;
using MigraDoc.DocumentObjectModel.Tables;
using System.Reflection;
using MigraDoc.DocumentObjectModel.Shapes.Charts;
using PIHelperSh.Core.Extensions;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Models.TableModels;
using PIHelperSh.PdfCreator.Models.TextModels;
using PIHelperSh.PdfCreator.Models.ImageModels;
using PIHelperSh.PdfCreator.Models.PieChartModel;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator
{
/// <summary>
/// </summary>
public class PdfCreator : IPdfCreator
{
private Document? _document;
private Section? _section;
private readonly Unit _borderWidth = 0.5;
/// <summary>
/// </summary>
public PdfCreator()
{
_document = new Document();
DefineStyles(_document);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
_section = _document.AddSection();
}
#region Внутренние методы
private static string GetListLavel(int level)
{
try
{
return level.CreateEnumFromValue<PdfStyleType>().GetValue<string>();
}
catch (Exception)
{
return PdfStyleType.Basic.GetValue<string>();
}
}
private void ConfigurateChartLegend(Chart chart, PdfLegendPosition position)
{
switch (position)
{
case PdfLegendPosition.Bottom:
chart.FooterArea.AddLegend();
break;
case PdfLegendPosition.Right:
chart.RightArea.AddLegend();
break;
case PdfLegendPosition.Left:
chart.LeftArea.AddLegend();
break;
case PdfLegendPosition.Top:
chart.TopArea.AddLegend();
break;
}
}
private static void DefineStyles(Document document)
{
#region Базовый стиль
var style = document.Styles["Normal"];
style.Font.Name = "Times New Roman";
style.Font.Size = 14;
#endregion
#region Стиль заголовка
style = document.Styles.AddStyle("NormalTitle", "Normal");
style.Font.Bold = true;
style.Font.Size = 18;
#endregion
#region Стиль жирный
style = document.Styles.AddStyle("NormalBold", "Normal");
style.Font.Bold = true;
#endregion
#region Маркированный список уровень 1
style = document.AddStyle("BulletList", "Normal");
style.ParagraphFormat.LeftIndent = "1.5cm";
style.ParagraphFormat.ListInfo = new ListInfo
{
ContinuePreviousList = true,//Продолжать список, который уже был ранее
ListType = ListType.BulletList1//Маркер
};
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(1.5), TabAlignment.Left);
style.ParagraphFormat.FirstLineIndent = "-0.5cm";
#endregion
#region Маркированный список уровень 2
style = document.AddStyle("BulletList2", "BulletList");
style.ParagraphFormat.LeftIndent = "3.0cm";
style.ParagraphFormat.ListInfo.ListType = ListType.BulletList2;
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(3.0), TabAlignment.Left);
#endregion
#region Маркированный список уровень 3
style = document.AddStyle("BulletList3", "BulletList");
style.ParagraphFormat.LeftIndent = "4.5cm";
style.ParagraphFormat.ListInfo.ListType = ListType.BulletList3;
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(4.5), TabAlignment.Left);
#endregion
#region Мелкий шрифт
style = document.Styles.AddStyle("NormalSmall", "Normal");
style.Font.Size = 11;
#endregion
}
private Paragraph? MakeParagraph(PdfParagraph pdfParagraph)
{
if (_section == null)
return null;
var paragraph = _section.AddParagraph(pdfParagraph.Text);
paragraph.Format.Alignment = pdfParagraph.ParagraphAlignment.GetValue<ParagraphAlignment>();
//Т.к стили могут назначаться по иному, тут будет вот так вот
return paragraph;
}
private Paragraph? MakeList(PdfList pdfList, int level)
{
Paragraph? last = null;
foreach (IPdfElement element in pdfList.Content)
{
if (element is PdfParagraph par)
{
var paragraph = MakeParagraph(par);
if (paragraph == null)
continue;
paragraph.Format.SpaceAfter = "0.3cm";
paragraph.Style = GetListLavel(level);
last = paragraph;
}
else if (element is PdfList ls)
{
last = MakeList(ls, level + 1);
}
}
return last;
}
private void ConfigurateParagraph(Paragraph paragraph, PdfParagraph properties)
{
paragraph.Format.Alignment = properties.ParagraphAlignment.GetValue<ParagraphAlignment>(); ;
if (properties.MarginAfter != PdfMargin.None) paragraph.Format.SpaceAfter = properties.MarginAfter.GetValue<string>();
paragraph.Style = properties.Style.GetValue<string>();
}
private void ConfigurateCell(Cell cell, string text, PdfAlignmentType alignment, PdfStyleType style, int? rightMerge = null, int? downMerge = null, bool dcw = false)
{
if (rightMerge.HasValue) cell.MergeRight = rightMerge.Value;
if (downMerge.HasValue)
{
cell.MergeDown = downMerge.Value;
cell.VerticalAlignment = VerticalAlignment.Center;
}
Paragraph paragraph = cell.AddParagraph(text);
ConfigurateParagraph(paragraph, new()
{
ParagraphAlignment = alignment,
Style = style,
MarginAfter = PdfMargin.None
});
cell.Borders.Left.Width = _borderWidth;
cell.Borders.Right.Width = _borderWidth;
cell.Borders.Top.Width = _borderWidth;
cell.Borders.Bottom.Width = _borderWidth;
if (dcw)
{
float columnNeeds = text.Length / 3.8f;
if (cell.Column.Width.Centimeter < columnNeeds)
cell.Column.Width = Unit.FromCentimeter(columnNeeds);
}
}
private Func<object?, object?> GetGetter(FieldInfo[] fields, PropertyInfo[] props, string Name)
{
var a = fields.FirstOrDefault(x => x.Name == Name);
if (a != null) return a.GetValue;
var b = props.FirstOrDefault(x => x.Name == Name);
if (b != null) return b.GetValue;
throw new KeyNotFoundException($"У объекта не найдено поле/свойство {Name}");
}
private List<Func<object?, object?>> MakeTableHeader<T>(Table table, PdfTable<T> header)
{
foreach (var item in header.Header!)
{
if (item is PdfTableColumnGroup group)
{
group.InnerColumns.ForEach(x => table.AddColumn(Unit.FromCentimeter(x.Size!.Value)));
}
else
{
table.AddColumn(Unit.FromCentimeter(item.Size!.Value));
}
}
Row upRow = table.AddRow();
Row downRow = table.AddRow();
upRow.HeadingFormat = downRow.HeadingFormat = true;
upRow.Format.Font.Bold = downRow.Format.Font.Bold = true;
int upColumn = 0;
int downColumn = 0;
Type eType = typeof(T);
var fields = eType.GetFields();
var prop = eType.GetProperties();
List<Func<object?, object?>> objectFields = new();
foreach (var item in header.Header)
{
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(upRow.Cells[upColumn], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: group.InnerColumns.Count - 1);
upColumn += group.InnerColumns.Count;
foreach (var ic in group.InnerColumns)
{
ConfigurateCell(downRow.Cells[downColumn], ic.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
objectFields.Add(GetGetter(fields, prop, ic.PropertyName));
downColumn++;
}
}
else
{
ConfigurateCell(upRow.Cells[upColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: 1);
ConfigurateCell(downRow.Cells[downColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
objectFields.Add(GetGetter(fields, prop, ((PdfTableColumn)item).PropertyName));
upColumn++;
downColumn++;
}
}
upRow.Height = header.HeaderHeaight;
downRow.Height = header.HeaderHeaight;
return objectFields;
}
private void MakeSimpleTableHeader(Table table, PDFSimpleTable header)
{
foreach (var item in header.Header!)
{
if (item is PdfTableColumnGroup group)
{
group.InnerColumns.ForEach(x => table.AddColumn(Unit.FromCentimeter(x.Size!.Value)));
}
else
{
table.AddColumn(Unit.FromCentimeter(item.Size!.Value));
}
}
Row upRow = table.AddRow();
Row downRow = table.AddRow();
upRow.HeadingFormat = downRow.HeadingFormat = true;
upRow.Format.Font.Bold = downRow.Format.Font.Bold = true;
int upColumn = 0;
int downColumn = 0;
foreach (var item in header.Header)
{
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(upRow.Cells[upColumn], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: group.InnerColumns.Count - 1);
upColumn += group.InnerColumns.Count;
foreach (var ic in group.InnerColumns)
{
ConfigurateCell(downRow.Cells[downColumn], ic.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
downColumn++;
}
}
else
{
ConfigurateCell(upRow.Cells[upColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: 1);
ConfigurateCell(downRow.Cells[downColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
upColumn++;
downColumn++;
}
}
}
private void MakeTableWithHederInRow<T>(Table table, PdfTable<T> header)
{
for (int i = 0; i < header.Records.Count + 2; i++)
{
_ = table.AddColumn("0.5cm");
}
Type tp = typeof(T);
var fields = tp.GetFields();
var props = tp.GetProperties();
foreach (var item in header.Header!)
{
Row? row = table.AddRow();
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(row.Cells[0], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: group.InnerColumns.Count - 1, dcw: true);
foreach (var innerParam in group.InnerColumns)
{
row ??= table.AddRow();//Первая(а точнее вторая) часть костыля с первой строкой
row.HeightRule = RowHeightRule.Exactly;
row.Height = Unit.FromCentimeter(innerParam.Size!.Value);//Устанавливаем высоту строки. Так просили...
ConfigurateCell(row.Cells[1], innerParam.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, dcw: true);
Func<object?, object?> getter = GetGetter(fields, props, innerParam.PropertyName);
for (int i = 0; i < header.Records.Count; i++)
{
ConfigurateCell(row.Cells[i + 2], getter(header.Records[i])!.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle, dcw: true);
}
row = null; //Это небольшой костыль, который позволяет не создавать новую строчку в самый первый раз(т.к. она же есть)
}
}
else if (item is PdfTableColumn column)
{
row.HeightRule = RowHeightRule.Exactly;
row.Height = Unit.FromCentimeter(column.Size!.Value);
ConfigurateCell(row.Cells[0], column.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: 1, dcw: true);
Func<object?, object?> getter = GetGetter(fields, props, column.PropertyName);
for (int i = 0; i < header.Records.Count; i++)
{
ConfigurateCell(row.Cells[i + 2], getter(header.Records[i])!.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle, dcw: true);
}
}
}
}
private void ConfiguratePieChart(Chart chart, PdfPieChartModel pieChart)
{
chart.Width = Unit.FromCentimeter(pieChart.Width);
chart.Height = Unit.FromCentimeter(pieChart.Height);
Paragraph p = chart.HeaderArea.AddParagraph(pieChart.ChartName);
ConfigurateParagraph(p, new()
{
Style = pieChart.HeaderStyle,
ParagraphAlignment = pieChart.HeaderAlignment,
MarginAfter = PdfMargin.None
});
chart.LineFormat.Visible = true;
ConfigurateChartLegend(chart, pieChart.LegendPosition);
chart.DataLabel.Position = DataLabelPosition.OutsideEnd;
}
#endregion
/// <summary>
/// Создаём параграф
/// </summary>
/// <param name="pdfParagraph">Модель параграфа, который вставляем</param>
public void AddParagraph(PdfParagraph pdfParagraph)
{
var paragraph = MakeParagraph(pdfParagraph);
if (paragraph == null)
return;
ConfigurateParagraph(paragraph, pdfParagraph);
}
/// <summary>
/// Создаём маркированный список
/// </summary>
/// <param name="pdfList">Модель списка(может быть многоуровневым). Max - 3 уровня</param>
public void AddList(PdfList pdfList)
{
if (_section == null)
{
return;
}
var lastP = MakeList(pdfList, 1);
if (lastP != null) lastP.Format.SpaceAfter = pdfList.MarginAfter.GetValue<string>();
}
/// <summary>
/// Создаёт таблицу, с шапкой из 2-х строк(с группировками)
/// </summary>
/// <typeparam name="T">Тип DTO, из которой берутся данные в таблицу</typeparam>
/// <param name="header">Модель самой таблицы</param>
/// <param name="rowHeaded"></param>
public void AddTable<T>(PdfTable<T> header, bool rowHeaded = false)
{
if (_document == null)
{
return;
}
if (rowHeaded)
{
_section!.PageSetup.Orientation = Orientation.Landscape;
_section.PageSetup.LeftMargin = 10;
MakeTableWithHederInRow(_document.LastSection.AddTable(), header);
return;
}
var table = _document.LastSection.AddTable();
if (rowHeaded)
{
MakeTableWithHederInRow(table, header);
return;
}
var maper = MakeTableHeader(table, header);
foreach (var item in header.Records)
{
var row = table.AddRow();
row.Height = item.height;
ConfigurateCell(row.Cells[0], maper[0](item.value)?.ToString()!, header.RecordHorizontalAlignment, header.HeaderStyle);
for (int i = 1; i < maper.Count; i++)
{
ConfigurateCell(row.Cells[i], maper[i](item.value)?.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle);
}
}
}
/// <summary>
/// Создаёт табличку, наподобие той, что с T, но проще
/// </summary>
/// <param name="tableData"></param>
public void AddSimpleTable(PDFSimpleTable tableData)
{
if (_document == null)
{
return;
}
if (tableData.ChangePageOrientation)
{
_section!.PageSetup.Orientation = Orientation.Landscape;
_section.PageSetup.LeftMargin = 10;
_section.PageSetup.BottomMargin = 5;
}
var table = _document.LastSection.AddTable();
MakeSimpleTableHeader(table, tableData);
foreach (var item in tableData.Rows)
{
var row = table.AddRow();
for (int i = 0; i < item.Items.Count; i++)
{
ConfigurateCell(row.Cells[i], item.Items[i], item.Alignment ?? tableData.RowHorizontalAlignment, item.Style ?? tableData.RowStyle);
}
}
if (tableData.MarginAfter != PdfMargin.None) table.Format.SpaceAfter = tableData.MarginAfter.GetValue<string>();
}
/// <summary>
/// Создаёт круговую диаграмму.
/// </summary>
/// <param name="pieChart">Модель для круговой диаграммы</param>
public void AddChart(PdfPieChartModel pieChart)
{
if (_document == null)
{
return;
}
Chart chart = new Chart(ChartType.Pie2D);
ConfiguratePieChart(chart, pieChart);
Series series = chart.SeriesCollection.AddSeries();
XSeries xseries = chart.XValues.AddXSeries();
foreach (var item in pieChart.DataSet)
{
var p = series.Add(item.Value);
xseries.Add(item.DisplayName);
if (item.Color.HasValue)
{
p.FillFormat.Color = new Color((uint)item.Color.Value.ToArgb());
}
}
_section?.Add(chart);
}
/// <summary>
/// Создаёт линейную диаграмму.
/// </summary>
/// <param name="pieHistogram">Модель для круговой диаграммы</param>
public void AddHistogram(PdfHistogramModel pieHistogram)
{
if (_document == null)
{
return;
}
Chart chart = new Chart(ChartType.Line);
ConfiguratePieChart(chart, pieHistogram);
XSeries xseries = chart.XValues.AddXSeries();
foreach (var val in pieHistogram.DataSet.First().Value.Select(v => v.Name))
xseries.Add(val);
foreach (var item in pieHistogram.DataSet)
{
Series series = chart.SeriesCollection.AddSeries();
series.Name = item.DisplayName;
series.Add(item.Value.Select(v => v.Value).ToArray());
if (item.Color.HasValue)
{
series.FillFormat.Color = new Color((uint)item.Color.Value.ToArgb());
}
}
chart.XAxis.MajorTickMark = TickMarkType.Outside;
chart.XAxis.Title.Caption = "X";
chart.XAxis.Title.Alignment = HorizontalAlignment.Right;
chart.YAxis.MajorTickMark = TickMarkType.Outside;
chart.YAxis.Title.Caption = "Y";
chart.YAxis.HasMajorGridlines = true;
_section?.Add(chart);
}
/// <summary>
/// Добавляем на лист изображение. Можно по пути, можно по потоку, можно по Base64 строке
/// </summary>
/// <param name="img">Модель одного изображения</param>
public void AddImage(PdfImage img)
{
if (_section == null) return;
var paragraph = _section.AddParagraph();//Это некоторый костыли. у изображения настроить выравнивание - это очень непросто. А вот так вот - можно
var image = paragraph.AddImage(img.Image.GetImageForMiraDoc());
if (img.Width.HasValue) image.Width = img.Width.Value;
if (img.Height.HasValue) image.Width = img.Height.Value;
paragraph.Format.Alignment = img.ImageAlignment.GetValue<ParagraphAlignment>();
paragraph.Format.SpaceAfter = img.MarginAfter.GetValue<string>();
}
/// <summary>
/// Метод сохранения созданного PDF документа
/// </summary>
/// <returns>Поток MemoryStream с документом</returns>
public MemoryStream SavePdf()
{
var renderer = new PdfDocumentRenderer(true)
{
Document = _document
};
renderer.RenderDocument();
MemoryStream file = new MemoryStream();
renderer.PdfDocument.Save(file, false);
file.Seek(0, SeekOrigin.Begin);
return file;
}
/// <summary>
/// Метод сохранения созданного PDF документа в файл
/// </summary>
/// <param name="filename">Имя файла и путь до него. Проверки на расширение нет</param>
public void SavePdf(string filename)
{
using Stream streamToWriteTo = File.Open(filename, FileMode.Create);
MemoryStream ms = SavePdf();
ms.Position = 0;
ms.CopyTo(streamToWriteTo);
}
}
}

View File

@ -0,0 +1,87 @@
# Библиотека PDF
Данная библиотека является надстройкой над MigraDoc и упрощает работу с PDF в контексте ряда операций.
Основным интерфейсом системы является **IPdfCreator**. На данный момент имеется единственная реализация - **PdfCreator**
## Основные методы IPdfCreator
- **AddParagraph(PdfParagraph paragraph)** данный метод предназначен для добавления на лист PDF параграфа текста. Принимает стандартную модель параграфа (о ней позже)
- **AddList(PdfList List)** данный метод предназначен для добавления на лист PDF маркированного списка. Принимает стандартныую модель списка ( о ней позже). `Поддерживается не более 3-х уровней!`
- **AddChart(PdfPieChartModel pieChart)** данный метод предназначен для добавления на лист PDF круговой диаграммы. Принимает модель диаграммы (о ней позже)
- **AddImage(PdfImage image)** данный метод предназначен для добавления на лист изображения. Принимает стандартную модель (о ней позже).
- **MemoryStream SavePdf()** данный метод сохраняет PDF документ и возвращает поток данных файла.
- **SavePdf(string filename)** данный метод сохраняет PDF документ на диске по пути из filename
- **AddTable\<T>(PdfTable\<T> header, bool rowHeaded = false)** данный метод позволяет добавлять на лист таблицу с шапкой и значениями. **rowHeaded** позволяет описать будет ли шапка в строках или в столбцах.
- **AddSimpleTable(PDFSimpleTable tableData)** данный метод позволяет добавть на лист простую табличку, строки в которой задаются пользователем самостоятельно.
## Основные модели
### PdfParagraph
Модель имеет следующие поля:
- **PdfMargin MarginAfter** - Отступ после параграфа. По умолчанию значение *Medium*
- **PdfAlignmentType ParagraphAlignment** - Выравнивание параграфа. По умолчанию значение *Left*
- **PdfStyleType Style** - Стиль параграфа. По сути объединение шрифта, жирности и отступов. По умолчанию - Basic. На данный момент имеются следующие (В дальнейшем планируется добавить возможность добавлять свои стили):
- *Basic*: Times New Roman 14
- *Title*: Times New Roman 18 жирный
- *Bold*: Times New Roman 14 жирный
- *ListLevel1*: Times New Roman 14 отступ 1.5cm
- *ListLevel2*: Times New Roman 14 отступ 3.0cm
- *ListLevel3*: Times New Roman 14 отступ 4.5cm
- *Small*: Times New Roman 11
- **string Text** - Текст параграфа
### PdfList
Данная модель позволяет создать многоуровневый список.
- **PdfMargin MarginAfter** - подробно описан ранее
- **List\<IPdfElement> Content** - элементы списка. Это могут быть как параграфы, так и другие списки
### PdfPieChartModel
Данная модель позволяет добавлять диаграмму на лист
- **double Width** - Ширина диаграммы на листе. По умолчанию 16.
- **double Height** - Высота диаграммы на листе. По умолчанию 12.
- **string ChartName** - Надпись над диаграммой. По сути её имя.
- **PdfStyleType HeaderStyle** - Стиль заголовка диаграммы.
- **PdfAlignmentType HeaderAlignment** - Выравнивание заголовка диаграммы.
- **PdfLegendPosition LegendPosition** - Местоположение легенды диаграммы.
- **List\<PdfPieChartData> DataSet** - Набор данных для диаграммы
### PdfPieChartData:
- **string DisplayName** - Имя для значения
- **double Value** - Значение
- **Color? Color** - Если нужно, цвет для значения
### PdfImage
Моделька для вывода изображения на лист. Имеет поля:
- **MigradocImage Image** - само изображение (обёртка)
- **int? Width** - Ширина изображения (если null - по размеру изображения)
- **int? Height** - Высота сообщения (если null - по соотношению сторон к длине)
- **PdfAlignmentType ImageAlignment** - Выравнивание изображения (по умолчанию - Left)
- **PdfMargin MarginAfter** - Отступ после. По умолчанию Medium
### MigradocImage
Обёртка, предназначенная для вывода изображений в MigraDock
Объект создаётся только с помощью статических методов:
- **CreateFromPath(string path)** - создаём из картинки, расположенной на компьютере по пути
- **MigradocImage CreateFromBase64(string base64)** - создаём из base64 строки, в которой зашифрована картинка
- **MigradocImage CreateFromStream(Stream stream)** - создаём из потока картинки
### PdfTable
Сейчас будет немного сложно. Данная модель предназначена для создания таблицы из коллекции объектов определённого типа, при этом шапка таблицы поддерживает 2 уровня(то есть, ряд столбцов могут быть сгруппированы общим заголовком). Поля:
- **List\<IPdfColumnItem>? Header** - модельки заголовков таблицы
- **PdfStyleType HeaderStyle** - Стиль заголовка таблицы. По умолчанию - Bold
- **PdfAlignmentType HeaderHorisontalAlignment** - Выравнивание текста внутри заголовков. По умолчанию - по центру
- **PdfStyleType RecordStyle** - Стиль текста записей строк таблицы. По умолчанию - Basic
- **PdfAlignmentType RecordHorisontalAlignment** - Выравнивае записей строк таблицы внутри ячейки. По умолчанию - Left
- **List\<T> Records** - список записей, из которых формируем таблицу
### PdfTableColumn
Столбец таблицы:
- **string? Title** - заголовок
- **float? Size** - ширина/высота ячейки
- **string PropertyName** - Имя свойства, которое будет заполнять данный столбец/строку
### PdfTableColumnGroup
Сгруппированные столбцы/строки заголовка
Поля:
- **string? Title** - заголовок этой группы
- **List\<PdfTableColumn> InnerColumns** - внутренние столбцы/строки
### PDFSimpleTable
- **List<IPdfColumnItem>? Header** - Заголовок таблицы. Уже был ранее описан
- **PdfStyleType HeaderStyle** - Стиль заголовка таблицы. По умолчанию - Bold
- **PdfAlignmentType HeaderHorisontalAlignment** - Выравнивание текста внутри заголовков. По умолчанию - по центру
- **List<PDFSimpleTableRow> Rows** - непосредственно строки таблицы
- **PdfStyleType RowStyle** - ситль текста таблицы по умолчанию. Может быть переопределён в каждой строке
- **PdfAlignmentType RowHorisontalAlignment** - выравнивание текста строки таблицы по умолчанию. Может быть переопределён
- **PdfMargin MarginAfter** - Отступ после. По умолчанию None
- **bool ChangePageOrientation** - флаг, меняем ли мы ориентацию листа на альбомную
### PDFSimpleTableRow
- **List<string>** Items - элементы строки таблицы
- **PdfStyleType? Style** - возможность переопределить стиль данной строки. Если Null - берётся стиль по умолчанию
- **PdfAlignmentType? Alignment** - возможность переопределить выравниваение данной строки. Если Null - берётся стиль по умолчанию

View File

@ -0,0 +1,94 @@
namespace TestCustomComponents.Forms
{
partial class Form2
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
customPdfTable1 = new Cop.Borovkov.Var3.Components.CustomPdfTable(components);
button1 = new Button();
button2 = new Button();
customPdfTableWithGrouping1 = new Cop.Borovkov.Var3.Components.CustomPdfTableWithGrouping(components);
button3 = new Button();
customPdfHistogram1 = new Cop.Borovkov.Var3.Components.CustomPdfHistogram(components);
SuspendLayout();
//
// button1
//
button1.Location = new Point(14, 16);
button1.Margin = new Padding(3, 4, 3, 4);
button1.Name = "button1";
button1.Size = new Size(86, 31);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
//
// button2
//
button2.Location = new Point(106, 16);
button2.Name = "button2";
button2.Size = new Size(94, 29);
button2.TabIndex = 1;
button2.Text = "button2";
button2.UseVisualStyleBackColor = true;
button2.Click += button2_Click;
//
// button3
//
button3.Location = new Point(206, 18);
button3.Name = "button3";
button3.Size = new Size(94, 29);
button3.TabIndex = 2;
button3.Text = "button3";
button3.UseVisualStyleBackColor = true;
button3.Click += button3_Click;
//
// Form2
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(914, 600);
Controls.Add(button3);
Controls.Add(button2);
Controls.Add(button1);
Margin = new Padding(3, 4, 3, 4);
Name = "Form2";
Text = "Form2";
ResumeLayout(false);
}
#endregion
private Cop.Borovkov.Var3.Components.CustomPdfTable customPdfTable1;
private Button button1;
private Button button2;
private Cop.Borovkov.Var3.Components.CustomPdfTableWithGrouping customPdfTableWithGrouping1;
private Button button3;
private Cop.Borovkov.Var3.Components.CustomPdfHistogram customPdfHistogram1;
}
}

View File

@ -0,0 +1,151 @@
using Cop.Borovkov.Var3.Components;
using Newtonsoft.Json.Serialization;
using PIHelperSh.PdfCreator.Enums;
using TestCustomComponents.Models;
namespace TestCustomComponents.Forms
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
customPdfTable1.SaveToPdf(new()
{
FilePath = @"E:\test\test.pdf",
Title = "Текст заголовка",
Tables = [new[,]
{
{ "00", "01", "02" },
{ "10", "11", "12" },
{ "20", "21", "22" },
}],
});
customPdfTable1.SaveToPdf(new()
{
FilePath = @"E:\test\test.pdf",
Title = "Текст заголовка",
Tables = [new[,]
{
{ "000", "001", "002" },
{ "010", "011", "012" },
{ "020", "021", "022" },
}, new[,]
{
{ "100", "101", "102" },
{ "110", "111", "112" },
{ "120", "121", "122" },
}],
});
}
private void button2_Click(object sender, EventArgs e)
{
customPdfTableWithGrouping1.SaveToPdf<TestModel>(new()
{
FilePath = @"C:\test\test.pdf",
Title = "Текст заголовка",
Columns = [
new() {
Header = "1",
PropertyName = nameof(TestModel.Height),
},
new() {
Header = "2",
PropertyName = nameof(TestModel.Name),
},
new() {
Header = "3",
PropertyName = nameof(TestModel.Id),
},
new() {
Header = "4",
PropertyName = nameof(TestModel.Age),
}
],
Rows = [
new() {
Value = new TestModel() {
Id = 1,
Name = "Vasya",
Age = 5,
Height = "low",
},
Height = 100f,
},
new() {
Value = new TestModel() {
Id = 2,
Name = "Masya",
Age = 8,
Height = "hi",
},
Height = 100f,
},
new() {
Value = new TestModel() {
Id = 3,
Name = "Kasya",
Age = 3,
Height = "low",
},
Height = 10f,
},
new() {
Value = new TestModel() {
Id = 4,
Name = "Asya",
Age = 100,
Height = "hi",
},
Height = 50f,
},
],
HeaderHeight = 100f,
});
}
private void button3_Click(object sender, EventArgs e)
{
customPdfHistogram1.SaveToPdf(new()
{
FilePath = @"C:\test\test.pdf",
DocumentTitle = "Текст заголовка",
HistogramTitle = "Заголовок диограммы",
Values = [
new(){
Name = "Линия1",
Points = [
new() { Name = "f3", Value = 2 },
new() { Name = "f4", Value = 3 },
new() { Name = "f5", Value = 5 },
new() { Name = "f6", Value = 8 },
new() { Name = "f7", Value = 13 },
new() { Name = "f8", Value = 21 },
new() { Name = "f9", Value = 34 },
new() { Name = "f10", Value = 55 },
new() { Name = "f11", Value = 89 },
],
},
new(){
Name = "Линия2",
Points = [
new() { Name = "r1", Value = 1 },
new() { Name = "r2", Value = 100 },
new() { Name = "r3", Value = 10 },
new() { Name = "r4", Value = 80 },
new() { Name = "r5", Value = 30 },
new() { Name = "r6", Value = 60 },
new() { Name = "r7", Value = 50 },
],
}
],
});
}
}
}

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="customPdfTable1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="customPdfTableWithGrouping1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>185, 17</value>
</metadata>
<metadata name="customPdfHistogram1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>446, 17</value>
</metadata>
</root>

View File

@ -4,8 +4,10 @@
{
public int Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public string Height { get; set; } = string.Empty;
}
}

View File

@ -1,3 +1,5 @@
using TestCustomComponents.Forms;
namespace TestCustomComponents
{
internal static class Program
@ -11,7 +13,7 @@ namespace TestCustomComponents
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
Application.Run(new Form2());
}
}
}