diff --git a/Components/Nonvisual/LegendAlignment.cs b/Components/Nonvisual/LegendAlignment.cs new file mode 100644 index 0000000..c7971ba --- /dev/null +++ b/Components/Nonvisual/LegendAlignment.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.Nonvisual +{ + public enum LegendAlignment + { + Top, + Bottom, + Left, + Right + } +} diff --git a/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs b/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs new file mode 100644 index 0000000..375d59a --- /dev/null +++ b/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs @@ -0,0 +1,36 @@ +namespace Components.Nonvisual +{ + partial class UserControlConfigurableTableDocument + { + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/Components/Nonvisual/UserControlConfigurableTableDocument.cs b/Components/Nonvisual/UserControlConfigurableTableDocument.cs new file mode 100644 index 0000000..d4c169d --- /dev/null +++ b/Components/Nonvisual/UserControlConfigurableTableDocument.cs @@ -0,0 +1,41 @@ +using MigraDoc.DocumentObjectModel.Tables; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.Nonvisual +{ + public partial class UserControlConfigurableTableDocument : Component + { + public UserControlConfigurableTableDocument() + { + InitializeComponent(); + } + + public UserControlConfigurableTableDocument(IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + + public void SaveToDocument(string filePath, string title, + List<(double width, string header, string propName)> columns, + double headerRowHeight, double rowHeight, List data) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("Empty string instead of path to file"); + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException("Title string is empty"); + if (data.Count == 0) throw new ArgumentNullException("Data list is empty"); + if (columns.Count == 0) throw new ArgumentNullException("Column info list is empty"); + if (rowHeight == 0D || headerRowHeight == 0D) throw new ArgumentNullException("Row height is empty"); + + SaveToPdf saver = new SaveToPdf(); + + saver.CreateConfigurableTableDocument(filePath, title, columns, headerRowHeight, rowHeight, data); + } + } +} diff --git a/Components/Nonvisual/UserControlHist.Designer.cs b/Components/Nonvisual/UserControlHist.Designer.cs new file mode 100644 index 0000000..de82aa1 --- /dev/null +++ b/Components/Nonvisual/UserControlHist.Designer.cs @@ -0,0 +1,36 @@ +namespace Components.Nonvisual +{ + partial class UserControlHist + { + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/Components/Nonvisual/UserControlHist.cs b/Components/Nonvisual/UserControlHist.cs new file mode 100644 index 0000000..1bf0a63 --- /dev/null +++ b/Components/Nonvisual/UserControlHist.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Components.Nonvisual +{ + public partial class UserControlHist : Component + { + public UserControlHist() + { + InitializeComponent(); + } + + public UserControlHist(IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + + public void CreateHist(string filePath, string title, string histTitle, + LegendAlignment alignment, Dictionary data) + { + if (string.IsNullOrEmpty(filePath)) + throw new ArgumentException("Filename cannot be empty"); + if (string.IsNullOrEmpty(title)) + throw new ArgumentException("Title cannot be empty"); + if (string.IsNullOrEmpty(histTitle)) + throw new ArgumentException("Hist title cannot be empty"); + if (data.Count == 0) + throw new ArgumentException("Data cannot be empty"); + + SaveToPdf saver = new SaveToPdf(); + saver.CreateHist(filePath, title, histTitle, alignment, data); + } + } +} diff --git a/Components/Nonvisual/UserControlTableDocument.cs b/Components/Nonvisual/UserControlTableDocument.cs new file mode 100644 index 0000000..c05bb07 --- /dev/null +++ b/Components/Nonvisual/UserControlTableDocument.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components +{ + public partial class UserControlTableDocument : Component + { + public UserControlTableDocument() + { + InitializeComponent(); + } + + public UserControlTableDocument(IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + + public void SaveToDocument(string filePath, string header, List tables) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("Empty string instead of path to file"); + if (string.IsNullOrEmpty(header)) throw new ArgumentNullException("Header string is empty"); + if (tables.Count == 0) throw new ArgumentNullException("Table list is empty"); + + SaveToPdf saver = new SaveToPdf(); + + saver.CreateTableDocumentWithContext(filePath, header, tables); + } + } +} diff --git a/Components/SaveToPdf.cs b/Components/SaveToPdf.cs new file mode 100644 index 0000000..2f9dd31 --- /dev/null +++ b/Components/SaveToPdf.cs @@ -0,0 +1,284 @@ +using MigraDoc.DocumentObjectModel; +using MigraDoc.DocumentObjectModel.Tables; +using MigraDoc.Rendering; +using Components.SaveToPdfHelpers; +using System.Text; +using System.Security.Cryptography; +using MigraDoc.DocumentObjectModel.Shapes.Charts; +using Components.Nonvisual; +using System.IO; + +namespace Components +{ + internal class SaveToPdf + { + private Document? _document; + private Section? _section; + private Table? _table; + public void CreateTableDocumentWithContext(string title, string path, List tables) + { + _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + DefineStyles(_document); + _section = _document.AddSection(); + + CreateParagraph(new PdfParagraph + { + Text = title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + foreach (var table in tables) + { + if (table.Length == 0) continue; + + CreateTable(Enumerable.Repeat("3cm", table[0].Length).ToList()); + + foreach (var row in table) + { + CreateRow(new PdfRowParameters + { + Texts = row.ToList(), + Style = "Normal", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + } + + CreateParagraph(new PdfParagraph()); + } + + var renderer = new PdfDocumentRenderer(true) + { + Document = _document + }; + renderer.RenderDocument(); + renderer.PdfDocument.Save(path); + } + + public void CreateConfigurableTableDocument(string path, string title, + List<(double width, string header, string propName)> columns, double headerRowHeight, + double rowHeight, List data) + { + _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + DefineStyles(_document); + _section = _document.AddSection(); + + CreateParagraph(new PdfParagraph + { + Text = title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + CreateTable(columns.Select(c => c.width).ToList()); + + CreateRow(new PdfRowParameters + { + Texts = columns.Select(c => c.header).ToList(), + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Center, + RowHeight = headerRowHeight, + }); + + foreach (var obj in data) + { + List cells = new List(); + + for (int i = 0; i < columns.Count; i++) + { + var propName = columns[i].propName; + var prop = typeof(T).GetProperty(propName); + if (prop is null) + { + throw new ArgumentException($"Failed to find property {prop} in provided objects class"); + } + var propVal = prop.GetValue(obj)?.ToString(); + if (propVal is not null) + { + cells.Add(propVal); + } + } + + CreateRow(new PdfRowParameters + { + Texts = cells, + Style = "Normal", + ParagraphAlignment = PdfParagraphAlignmentType.Left, + FirstCellStyle = "NormalTitle", + FirstCellParagraphAlignment = PdfParagraphAlignmentType.Center, + RowHeight = rowHeight, + }); + } + + CreateParagraph(new PdfParagraph()); + + var renderer = new PdfDocumentRenderer(true) + { + Document = _document + }; + renderer.RenderDocument(); + renderer.PdfDocument.Save(path); + } + + public void CreateHist(string filePath, string title, string histTitle, + LegendAlignment alignment, Dictionary data) + { + _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + DefineStyles(_document); + _section = _document.AddSection(); + + CreateParagraph(new PdfParagraph + { + Text = title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + CreateChart(new PdfChartParameters + { + Data = data, + Title = histTitle, + LegendAlignment = alignment, + }); + + var renderer = new PdfDocumentRenderer(true) + { + Document = _document + }; + renderer.RenderDocument(); + renderer.PdfDocument.Save(filePath); + } + private static ParagraphAlignment GetParagraphAlignment(PdfParagraphAlignmentType type) + { + return type switch + { + PdfParagraphAlignmentType.Center => ParagraphAlignment.Center, + PdfParagraphAlignmentType.Left => ParagraphAlignment.Left, + PdfParagraphAlignmentType.Right => ParagraphAlignment.Right, + _ => ParagraphAlignment.Justify, + }; + } + private static void DefineStyles(Document document) + { + var style = document.Styles["Normal"]; + style.Font.Name = "Times New Roman"; + style.Font.Size = 14; + style = document.Styles.AddStyle("NormalTitle", "Normal"); + style.Font.Bold = true; + } + protected void CreateParagraph(PdfParagraph pdfParagraph) + { + if (_section == null) + { + return; + } + var paragraph = _section.AddParagraph(pdfParagraph.Text); + paragraph.Format.SpaceAfter = "1cm"; + paragraph.Format.Alignment = GetParagraphAlignment(pdfParagraph.ParagraphAlignment); + paragraph.Style = pdfParagraph.Style; + } + protected void CreateTable(List columns) + { + if (_document == null) + { + return; + } + _table = _document.LastSection.AddTable(); + foreach (var elem in columns) + { + _table.AddColumn(elem); + } + } + + protected void CreateTable(List columnWidths) + { + if (_document == null) + { + return; + } + _table = _document.LastSection.AddTable(); + foreach (var elem in columnWidths) + { + _table.AddColumn(Unit.FromCentimeter(elem)); + } + } + protected void CreateRow(PdfRowParameters rowParameters) + { + if (_table == null) + { + return; + } + var row = _table.AddRow(); + for (int i = 0; i < rowParameters.Texts.Count; ++i) + { + row.Cells[i].AddParagraph(rowParameters.Texts[i]); + if (i == 0 && !string.IsNullOrEmpty(rowParameters.FirstCellStyle)) + { + row.Cells[i].Style = rowParameters.FirstCellStyle; + } + else if (!string.IsNullOrEmpty(rowParameters.Style)) + { + row.Cells[i].Style = rowParameters.Style; + } + if (i == 0 && rowParameters.FirstCellParagraphAlignment != null) + { + row.Cells[i].Format.Alignment = GetParagraphAlignment((PdfParagraphAlignmentType)rowParameters.FirstCellParagraphAlignment); + } + else + { + row.Cells[i].Format.Alignment = GetParagraphAlignment(rowParameters.ParagraphAlignment); + } + if (rowParameters.RowHeight is not null) + { + row.Height = Unit.FromCentimeter((double)rowParameters.RowHeight); + } + Unit borderWidth = 0.5; + row.Cells[i].Borders.Left.Width = borderWidth; + row.Cells[i].Borders.Right.Width = borderWidth; + row.Cells[i].Borders.Top.Width = borderWidth; + row.Cells[i].Borders.Bottom.Width = borderWidth; + row.Cells[i].VerticalAlignment = VerticalAlignment.Center; + } + } + + protected void CreateChart(PdfChartParameters chartParams) + { + if (_section == null) + return; + + Chart chart = new Chart(ChartType.Bar2D); + chart.Width = "15cm"; + chart.Height = "8cm"; + + foreach (var dataSeries in chartParams.Data) + { + Series series = chart.SeriesCollection.AddSeries(); + series.Name = dataSeries.Key; + series.Add(dataSeries.Value.Select(x => (double)x).ToArray()); + } + + chart.TopArea.AddParagraph(chartParams.Title); + + chart.XAxis.MajorTickMark = TickMarkType.Outside; + chart.YAxis.MajorTickMark = TickMarkType.Outside; + chart.YAxis.HasMajorGridlines = true; + + _ = chartParams.LegendAlignment switch + { + LegendAlignment.Top => chart.TopArea.AddLegend(), + LegendAlignment.Right => chart.RightArea.AddLegend(), + LegendAlignment.Bottom => chart.BottomArea.AddLegend(), + LegendAlignment.Left => chart.LeftArea.AddLegend(), + _ => chart.BottomArea.AddLegend(), + }; + + chart.PlotArea.LineFormat.Width = 1; + chart.PlotArea.LineFormat.Visible = true; + + _section.Add(chart); + } + } +} diff --git a/Components/SaveToPdfHelpers/PdfChartParameters.cs b/Components/SaveToPdfHelpers/PdfChartParameters.cs new file mode 100644 index 0000000..4a3d033 --- /dev/null +++ b/Components/SaveToPdfHelpers/PdfChartParameters.cs @@ -0,0 +1,18 @@ +using Components.Nonvisual; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.SaveToPdfHelpers +{ + internal class PdfChartParameters + { + public Dictionary Data = new(); + + public string Title = string.Empty; + + public LegendAlignment LegendAlignment = LegendAlignment.Bottom; + } +} diff --git a/Components/SaveToPdfHelpers/PdfRowParameters.cs b/Components/SaveToPdfHelpers/PdfRowParameters.cs new file mode 100644 index 0000000..a540d6d --- /dev/null +++ b/Components/SaveToPdfHelpers/PdfRowParameters.cs @@ -0,0 +1,14 @@ + +namespace Components.SaveToPdfHelpers +{ + public class PdfRowParameters + { + public List Texts { get; set; } = new(); + public string Style { get; set; } = string.Empty; + + public double? RowHeight { get; set; } + public PdfParagraphAlignmentType ParagraphAlignment { get; set; } + public string FirstCellStyle { get; set; } = string.Empty; + public PdfParagraphAlignmentType? FirstCellParagraphAlignment { get; set; } + } +} diff --git a/TestingForm/FormMain.Designer.cs b/TestingForm/FormMain.Designer.cs new file mode 100644 index 0000000..790b31a --- /dev/null +++ b/TestingForm/FormMain.Designer.cs @@ -0,0 +1,92 @@ +namespace TestingForm +{ + partial class FormMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + userControlTableDocument1 = new Components.UserControlTableDocument(components); + button1 = new Button(); + button2 = new Button(); + userControlConfigurableTableDocument1 = new Components.Nonvisual.UserControlConfigurableTableDocument(components); + button3 = new Button(); + userControlHist1 = new Components.Nonvisual.UserControlHist(components); + SuspendLayout(); + // + // button1 + // + button1.Location = new Point(12, 12); + button1.Name = "button1"; + button1.Size = new Size(188, 65); + button1.TabIndex = 0; + button1.Text = "Сохранить первый компонент"; + button1.UseVisualStyleBackColor = true; + button1.Click += button1_Click; + // + // button2 + // + button2.Location = new Point(218, 12); + button2.Name = "button2"; + button2.Size = new Size(188, 65); + button2.TabIndex = 1; + button2.Text = "Сохранить второй компонент"; + button2.UseVisualStyleBackColor = true; + button2.Click += button2_Click; + // + // button3 + // + button3.Location = new Point(433, 12); + button3.Name = "button3"; + button3.Size = new Size(188, 65); + button3.TabIndex = 2; + button3.Text = "Сохранить третий компонент"; + button3.UseVisualStyleBackColor = true; + button3.Click += button3_Click; + // + // FormMain + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(800, 450); + Controls.Add(button3); + Controls.Add(button2); + Controls.Add(button1); + Name = "FormMain"; + Text = "Testing form"; + ResumeLayout(false); + } + + #endregion + + private Components.UserControlTableDocument userControlTableDocument1; + private Button button1; + private Button button2; + private Components.Nonvisual.UserControlConfigurableTableDocument userControlConfigurableTableDocument1; + private Button button3; + private Components.Nonvisual.UserControlHist userControlHist1; + } +} diff --git a/TestingForm/FormMain.cs b/TestingForm/FormMain.cs new file mode 100644 index 0000000..9d6d3ad --- /dev/null +++ b/TestingForm/FormMain.cs @@ -0,0 +1,116 @@ +using Components.Nonvisual; + +namespace TestingForm +{ + public partial class FormMain : Form + { + public FormMain() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + + List tables = new List(); + string[][] table1 = new string[2][]; + table1[0] = new string[] { "test", "test 2" }; + table1[1] = new string[] { "test next", "test next 2" }; + + string[][] table2 = new string[3][]; + table2[0] = new string[] { "ttt", "ttt 2", "ttt 3" }; + table2[1] = new string[] { "ttt next", "ttt next 2" }; + table2[2] = new string[] { "next" }; + + tables.Add(table1); + tables.Add(table2); + + try + { + userControlTableDocument1.SaveToDocument( + " ", + dialog.FileName, + tables + ); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } + + private void button2_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + List users = new List + { + new TestUser(1, "user 1", 20, 50000), + new TestUser(2, "user 2", 19, 30000), + new TestUser(3, "user 3", 26, 70000) + }; + + try + { + userControlConfigurableTableDocument1.SaveToDocument( + dialog.FileName, + " ", + new List<(double width, string header, string propName)> + { + (2.0, "ID", "Id"), + (6.0, "", "Name"), + (2.0, "", "Age"), + (6.0, "", "Salary"), + }, + 7.0, + 2.0, + users + ); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } + + private void button3_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + Dictionary data = new Dictionary + { + { "test 1", new int[] {1, 2, 3, 6, 7, 9} }, + { "test 2", new int[] {6, 8, 11, 16, 4, 2} }, + }; + + try + { + userControlHist1.CreateHist(dialog.FileName, "", "Test", LegendAlignment.Bottom, data); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } + } +} diff --git a/TestingForm/FormMain.resx b/TestingForm/FormMain.resx new file mode 100644 index 0000000..df6383c --- /dev/null +++ b/TestingForm/FormMain.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 222, 17 + + + 496, 17 + + \ No newline at end of file diff --git a/TestingForm/TestUser.cs b/TestingForm/TestUser.cs new file mode 100644 index 0000000..7cc5612 --- /dev/null +++ b/TestingForm/TestUser.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestingForm +{ + public class TestUser + { + public int Id { get; private set; } + public string Name { get; private set; } + public int Age { get; private set; } + public int Salary { get; private set; } + + public TestUser(int id, string name, int age, int salary) + { + Id = id; + Name = name; + Age = age; + Salary = salary; + } + } +}