using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Windows.Forms; [assembly: AssemblyTitle("PANDA")] [assembly: AssemblyDescription("Pseudonymisierung alphanumerischer Nutzdaten durch Alphabetverschiebung")] [assembly: AssemblyProduct("PANDA")] [assembly: AssemblyCompany("PANDA")] [assembly: AssemblyVersion("1.4.0.0")] [assembly: AssemblyFileVersion("1.4.0.0")] namespace Panda { internal static class Program { [STAThread] private static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); if (args.Length == 2 && string.Equals(args[0], "--screenshot", StringComparison.OrdinalIgnoreCase)) { using (var form = new MainForm()) { form.Size = new Size(1320, 820); form.Show(); form.LoadPreviewData(); Application.DoEvents(); using (var bitmap = new Bitmap(form.Width, form.Height)) { form.DrawToBitmap(bitmap, new Rectangle(Point.Empty, form.Size)); bitmap.Save(args[1], System.Drawing.Imaging.ImageFormat.Png); } form.Close(); } return; } if (args.Length == 3 && string.Equals(args[0], "--wizard-screenshot", StringComparison.OrdinalIgnoreCase)) { using (var wizard = new ImportWizardForm(args[1])) { wizard.Show(); Application.DoEvents(); wizard.UncheckLastColumnForPreview(); Application.DoEvents(); using (var bitmap = new Bitmap(wizard.Width, wizard.Height)) { wizard.DrawToBitmap(bitmap, new Rectangle(Point.Empty, wizard.Size)); bitmap.Save(args[2], System.Drawing.Imaging.ImageFormat.Png); } wizard.Close(); } return; } if (args.Length == 2 && string.Equals(args[0], "--settings-screenshot", StringComparison.OrdinalIgnoreCase)) { var previewSettings = new AppSettings { DefaultShift = 6, ConfirmBeforeShift = true }; using (var settingsForm = new SettingsForm(previewSettings)) { settingsForm.Show(); Application.DoEvents(); using (var bitmap = new Bitmap(settingsForm.Width, settingsForm.Height)) { settingsForm.DrawToBitmap(bitmap, new Rectangle(Point.Empty, settingsForm.Size)); bitmap.Save(args[1], System.Drawing.Imaging.ImageFormat.Png); } settingsForm.Close(); } return; } Application.Run(new MainForm()); } } internal sealed class CsvDocument { public List Headers = new List(); public List> Rows = new List>(); public char Delimiter; public bool FirstRowIsHeader; } internal static class CsvCodec { public static CsvDocument Load(string path, bool firstRowIsHeader) { string text; using (var reader = new StreamReader(path, Encoding.UTF8, true)) text = reader.ReadToEnd(); char delimiter = DetectDelimiter(text); var records = Parse(text, delimiter); var document = new CsvDocument { Delimiter = delimiter, FirstRowIsHeader = firstRowIsHeader }; int columnCount = records.Count == 0 ? 0 : records.Max(row => row.Count); if (firstRowIsHeader && records.Count > 0) { for (int column = 0; column < columnCount; column++) { string value = column < records[0].Count ? records[0][column] : string.Empty; document.Headers.Add(string.IsNullOrWhiteSpace(value) ? "Spalte " + (column + 1) : value); } records.RemoveAt(0); } else { for (int column = 0; column < columnCount; column++) document.Headers.Add("Spalte " + (column + 1)); } foreach (var record in records) { while (record.Count < columnCount) record.Add(string.Empty); document.Rows.Add(record); } return document; } public static void Save(string path, CsvDocument document, IList> rows) { using (var writer = new StreamWriter(path, false, new UTF8Encoding(true))) { if (document.FirstRowIsHeader) WriteRecord(writer, document.Headers, document.Delimiter); foreach (var row in rows) WriteRecord(writer, row, document.Delimiter); } } public static CsvDocument SelectColumns(CsvDocument source, IList selectedColumns) { var result = new CsvDocument { Delimiter = source.Delimiter, FirstRowIsHeader = source.FirstRowIsHeader }; foreach (int column in selectedColumns) { if (column < 0 || column >= source.Headers.Count) throw new ArgumentOutOfRangeException("selectedColumns"); result.Headers.Add(source.Headers[column]); } foreach (var sourceRow in source.Rows) { var row = new List(); foreach (int column in selectedColumns) row.Add(column < sourceRow.Count ? sourceRow[column] : string.Empty); result.Rows.Add(row); } return result; } private static void WriteRecord(TextWriter writer, IEnumerable values, char delimiter) { writer.WriteLine(string.Join(delimiter.ToString(), values.Select(value => Escape(value ?? string.Empty, delimiter)))); } private static string Escape(string value, char delimiter) { if (value.IndexOfAny(new[] { delimiter, '"', '\r', '\n' }) < 0) return value; return "\"" + value.Replace("\"", "\"\"") + "\""; } internal static char DetectDelimiter(string text) { string firstRecord = GetFirstLogicalRecord(text); char[] candidates = { ';', ',', '\t' }; char best = ';'; int bestCount = -1; foreach (char candidate in candidates) { int count = CountOutsideQuotes(firstRecord, candidate); if (count > bestCount) { bestCount = count; best = candidate; } } return best; } private static string GetFirstLogicalRecord(string text) { var builder = new StringBuilder(); bool quoted = false; for (int index = 0; index < text.Length; index++) { char current = text[index]; if (current == '"') { if (quoted && index + 1 < text.Length && text[index + 1] == '"') { builder.Append("\"\""); index++; continue; } quoted = !quoted; } if (!quoted && (current == '\r' || current == '\n')) break; builder.Append(current); } return builder.ToString(); } private static int CountOutsideQuotes(string text, char delimiter) { bool quoted = false; int count = 0; for (int index = 0; index < text.Length; index++) { if (text[index] == '"') { if (quoted && index + 1 < text.Length && text[index + 1] == '"') { index++; continue; } quoted = !quoted; } else if (!quoted && text[index] == delimiter) { count++; } } return count; } internal static List> Parse(string text, char delimiter) { var rows = new List>(); var row = new List(); var field = new StringBuilder(); bool quoted = false; for (int index = 0; index < text.Length; index++) { char current = text[index]; if (quoted) { if (current == '"') { if (index + 1 < text.Length && text[index + 1] == '"') { field.Append('"'); index++; } else { quoted = false; } } else { field.Append(current); } } else if (current == '"' && field.Length == 0) { quoted = true; } else if (current == delimiter) { row.Add(field.ToString()); field.Clear(); } else if (current == '\r' || current == '\n') { if (current == '\r' && index + 1 < text.Length && text[index + 1] == '\n') index++; row.Add(field.ToString()); field.Clear(); rows.Add(row); row = new List(); } else { field.Append(current); } } if (field.Length > 0 || row.Count > 0) { row.Add(field.ToString()); rows.Add(row); } return rows; } } internal static class LetterShifter { public static string Shift(string value, int amount) { if (string.IsNullOrEmpty(value) || amount == 0) return value; var result = new StringBuilder(value.Length); foreach (char character in value) { if (character >= 'A' && character <= 'Z') result.Append(ShiftInRange(character, 'A', 26, amount)); else if (character >= 'a' && character <= 'z') result.Append(ShiftInRange(character, 'a', 26, amount)); else result.Append(character); } return result.ToString(); } private static char ShiftInRange(char value, char start, int length, int amount) { int shifted = ((value - start + amount) % length + length) % length; return (char)(start + shifted); } } internal sealed class AppSettings { public int DefaultShift = 1; public bool ConfirmBeforeShift = true; private static string SettingsPath { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PANDA", "settings.ini"); } } public static AppSettings Load() { try { if (File.Exists(SettingsPath)) return Parse(File.ReadAllLines(SettingsPath)); } catch { } return new AppSettings(); } public void Save() { string directory = Path.GetDirectoryName(SettingsPath); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); File.WriteAllLines(SettingsPath, Serialize(), new UTF8Encoding(false)); } internal static AppSettings Parse(IEnumerable lines) { var settings = new AppSettings(); foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith("#")) continue; int separator = line.IndexOf('='); if (separator <= 0) continue; string key = line.Substring(0, separator).Trim(); string value = line.Substring(separator + 1).Trim(); if (string.Equals(key, "DefaultShift", StringComparison.OrdinalIgnoreCase)) { int parsed; if (int.TryParse(value, out parsed)) settings.DefaultShift = Math.Max(1, Math.Min(25, parsed)); } else if (string.Equals(key, "ConfirmBeforeShift", StringComparison.OrdinalIgnoreCase)) { bool parsed; if (bool.TryParse(value, out parsed)) settings.ConfirmBeforeShift = parsed; } } return settings; } internal string[] Serialize() { return new[] { "DefaultShift=" + DefaultShift, "ConfirmBeforeShift=" + ConfirmBeforeShift }; } } internal sealed class SettingsForm : Form { private readonly Color Navy = Color.FromArgb(24, 38, 58); private readonly Color Blue = Color.FromArgb(41, 112, 255); private readonly Color Background = Color.FromArgb(244, 247, 251); private readonly Color Muted = Color.FromArgb(94, 108, 128); private readonly NumericUpDown defaultShiftNumeric = new NumericUpDown(); private readonly CheckBox confirmationCheckBox = new CheckBox(); public int DefaultShift { get { return (int)defaultShiftNumeric.Value; } } public bool ConfirmBeforeShift { get { return confirmationCheckBox.Checked; } } public SettingsForm(AppSettings settings) { Text = "PANDA – Einstellungen"; StartPosition = FormStartPosition.CenterParent; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ClientSize = new Size(560, 330); BackColor = Background; Font = new Font("Segoe UI", 9F); Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application; BuildLayout(settings); } private void BuildLayout(AppSettings settings) { var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3, Padding = new Padding(24, 20, 24, 18), BackColor = Background }; root.RowStyles.Add(new RowStyle(SizeType.Absolute, 78)); root.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 58)); Controls.Add(root); var heading = new Panel { Dock = DockStyle.Fill }; heading.Controls.Add(new Label { Text = "Einstellungen", Font = new Font("Segoe UI Semibold", 18F), ForeColor = Navy, AutoSize = true, Location = new Point(0, 0) }); heading.Controls.Add(new Label { Text = "Lege die Standardwerte für zukünftige Umwandlungen fest.", ForeColor = Muted, AutoSize = true, Location = new Point(2, 40) }); root.Controls.Add(heading, 0, 0); var card = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 4, BackColor = Color.White, Padding = new Padding(18, 14, 18, 14), Margin = new Padding(0, 0, 0, 12) }; card.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); card.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90)); card.RowStyles.Add(new RowStyle(SizeType.Absolute, 28)); card.RowStyles.Add(new RowStyle(SizeType.Absolute, 38)); card.RowStyles.Add(new RowStyle(SizeType.Absolute, 16)); card.RowStyles.Add(new RowStyle(SizeType.Absolute, 42)); var shiftLabel = new Label { Text = "Standard-Zählwert", Font = new Font("Segoe UI Semibold", 10F), ForeColor = Navy, AutoSize = true, Anchor = AnchorStyles.Left }; card.Controls.Add(shiftLabel, 0, 0); card.SetColumnSpan(shiftLabel, 2); card.Controls.Add(new Label { Text = "Dieser Wert wird beim Programmstart und nach dem Speichern voreingestellt.", ForeColor = Muted, AutoSize = true, Anchor = AnchorStyles.Left }, 0, 1); defaultShiftNumeric.Minimum = 1; defaultShiftNumeric.Maximum = 25; defaultShiftNumeric.Value = Math.Max(1, Math.Min(25, settings.DefaultShift)); defaultShiftNumeric.TextAlign = HorizontalAlignment.Center; defaultShiftNumeric.Dock = DockStyle.Fill; defaultShiftNumeric.Margin = new Padding(8, 5, 0, 5); card.Controls.Add(defaultShiftNumeric, 1, 1); confirmationCheckBox.Text = "Vor jeder Umwandlung eine Bestätigung mit dem gewählten Zählwert anzeigen"; confirmationCheckBox.Checked = settings.ConfirmBeforeShift; confirmationCheckBox.AutoSize = true; confirmationCheckBox.ForeColor = Navy; confirmationCheckBox.Anchor = AnchorStyles.Left; card.SetColumnSpan(confirmationCheckBox, 2); card.Controls.Add(confirmationCheckBox, 0, 3); root.Controls.Add(card, 0, 1); var footer = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 4, RowCount = 1, Padding = new Padding(0, 10, 0, 0) }; footer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130)); footer.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); var cancelButton = new Button { Text = "Abbrechen", DialogResult = DialogResult.Cancel, Dock = DockStyle.Fill, FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Navy, Margin = new Padding(0) }; cancelButton.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230); var saveButton = new Button { Text = "Speichern", DialogResult = DialogResult.OK, Dock = DockStyle.Fill, FlatStyle = FlatStyle.Flat, BackColor = Blue, ForeColor = Color.White, Margin = new Padding(0) }; saveButton.FlatAppearance.BorderSize = 0; footer.Controls.Add(cancelButton, 1, 0); footer.Controls.Add(saveButton, 3, 0); root.Controls.Add(footer, 0, 2); AcceptButton = saveButton; CancelButton = cancelButton; } } internal sealed class ImportWizardForm : Form { private readonly string csvPath; private readonly Color Navy = Color.FromArgb(24, 38, 58); private readonly Color Blue = Color.FromArgb(41, 112, 255); private readonly Color Background = Color.FromArgb(244, 247, 251); private readonly Color Muted = Color.FromArgb(94, 108, 128); private readonly CheckBox headerCheckBox = new CheckBox(); private readonly Label formatLabel = new Label(); private readonly Label selectionLabel = new Label(); private readonly CheckedListBox columnList = new CheckedListBox(); private readonly DataGridView previewGrid = new DataGridView(); private readonly Button importButton = new Button(); private CsvDocument loadedDocument; public CsvDocument SelectedDocument { get; private set; } public ImportWizardForm(string path) { csvPath = path; Text = "PANDA Import-Assistent"; StartPosition = FormStartPosition.CenterParent; MinimumSize = new Size(900, 600); Size = new Size(1040, 680); BackColor = Background; Font = new Font("Segoe UI", 9F); Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application; BuildLayout(); ReloadPreview(true); } private void BuildLayout() { var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 4, Padding = new Padding(22, 18, 22, 18), BackColor = Background }; root.RowStyles.Add(new RowStyle(SizeType.Absolute, 72)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 72)); root.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 58)); Controls.Add(root); var titlePanel = new Panel { Dock = DockStyle.Fill }; titlePanel.Controls.Add(new Label { Text = "CSV-Import vorbereiten", Font = new Font("Segoe UI Semibold", 18F), ForeColor = Navy, AutoSize = true, Location = new Point(0, 0) }); titlePanel.Controls.Add(new Label { Text = "Prüfe das Format und wähle die Spalten aus, die PANDA übernehmen soll.", ForeColor = Muted, AutoSize = true, Location = new Point(2, 39) }); root.Controls.Add(titlePanel, 0, 0); var filePanel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 3, RowCount = 2, BackColor = Color.White, Padding = new Padding(14, 8, 14, 8), Margin = new Padding(0, 0, 0, 12) }; filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 55)); filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 240)); filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 45)); filePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 24)); filePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 24)); var stepOne = new Label { Text = "1 DATEI UND FORMAT", Font = new Font("Segoe UI Semibold", 9F), ForeColor = Blue, AutoSize = true, Anchor = AnchorStyles.Left }; filePanel.Controls.Add(stepOne, 0, 0); filePanel.SetColumnSpan(stepOne, 3); filePanel.Controls.Add(new Label { Text = Path.GetFileName(csvPath), ForeColor = Navy, AutoEllipsis = true, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleLeft }, 0, 1); headerCheckBox.Text = "Erste Zeile enthält Überschriften"; headerCheckBox.Checked = true; headerCheckBox.AutoSize = true; headerCheckBox.ForeColor = Navy; headerCheckBox.Anchor = AnchorStyles.Left; headerCheckBox.CheckedChanged += delegate { ReloadPreview(false); }; filePanel.Controls.Add(headerCheckBox, 1, 1); formatLabel.ForeColor = Muted; formatLabel.AutoEllipsis = true; formatLabel.Dock = DockStyle.Fill; formatLabel.TextAlign = ContentAlignment.MiddleRight; filePanel.Controls.Add(formatLabel, 2, 1); root.Controls.Add(filePanel, 0, 1); var content = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 2, BackColor = Background, Margin = new Padding(0) }; content.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 280)); content.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); content.RowStyles.Add(new RowStyle(SizeType.Absolute, 42)); content.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); content.Controls.Add(CreateSectionHeader("2 SPALTEN AUSWÄHLEN", false), 0, 0); content.Controls.Add(CreateSectionHeader("VORSCHAU DER DATEI", true), 1, 0); root.Controls.Add(content, 0, 2); var selectionPanel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 3, BackColor = Color.White, Padding = new Padding(12, 10, 12, 12), Margin = new Padding(0, 0, 7, 0) }; selectionPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); selectionPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); selectionPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 30)); selectionPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); selectionPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 26)); var allButton = CreateLinkButton("Alle auswählen"); allButton.Click += delegate { SetAllColumns(true); }; var noneButton = CreateLinkButton("Auswahl aufheben"); noneButton.Click += delegate { SetAllColumns(false); }; selectionPanel.Controls.Add(allButton, 0, 0); selectionPanel.Controls.Add(noneButton, 1, 0); columnList.Dock = DockStyle.Fill; columnList.BorderStyle = BorderStyle.None; columnList.CheckOnClick = true; columnList.ForeColor = Navy; columnList.BackColor = Color.White; columnList.HorizontalScrollbar = true; columnList.ItemCheck += delegate(object sender, ItemCheckEventArgs args) { if (args.Index >= 0 && args.Index < previewGrid.Columns.Count) previewGrid.Columns[args.Index].Visible = args.NewValue == CheckState.Checked; if (IsHandleCreated) BeginInvoke(new Action(UpdateSelectionStatus)); }; selectionPanel.SetColumnSpan(columnList, 2); selectionPanel.Controls.Add(columnList, 0, 1); selectionLabel.ForeColor = Muted; selectionLabel.Dock = DockStyle.Fill; selectionLabel.TextAlign = ContentAlignment.MiddleLeft; selectionPanel.SetColumnSpan(selectionLabel, 2); selectionPanel.Controls.Add(selectionLabel, 0, 2); content.Controls.Add(selectionPanel, 0, 1); ConfigurePreviewGrid(); content.Controls.Add(previewGrid, 1, 1); var footer = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 4, RowCount = 1, Padding = new Padding(0, 12, 0, 0) }; footer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 140)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10)); footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 140)); footer.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); importButton.Text = "Importieren"; importButton.Dock = DockStyle.Fill; importButton.Margin = new Padding(0); importButton.FlatStyle = FlatStyle.Flat; importButton.FlatAppearance.BorderSize = 0; importButton.BackColor = Blue; importButton.ForeColor = Color.White; importButton.Cursor = Cursors.Hand; importButton.Click += delegate { FinishImport(); }; var cancelButton = new Button { Text = "Abbrechen", DialogResult = DialogResult.Cancel, Dock = DockStyle.Fill, FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Navy, Margin = new Padding(0) }; cancelButton.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230); footer.Controls.Add(cancelButton, 1, 0); footer.Controls.Add(importButton, 3, 0); root.Controls.Add(footer, 0, 3); AcceptButton = importButton; CancelButton = cancelButton; } private Panel CreateSectionHeader(string text, bool preview) { var panel = new Panel { Dock = DockStyle.Fill, BackColor = preview ? Color.FromArgb(236, 243, 255) : Color.White, Margin = preview ? new Padding(7, 0, 0, 0) : new Padding(0, 0, 7, 0) }; panel.Controls.Add(new Label { Text = text, Font = new Font("Segoe UI Semibold", 9F), ForeColor = preview ? Blue : Navy, AutoSize = true, Location = new Point(12, 12) }); return panel; } private Button CreateLinkButton(string text) { var button = new Button { Text = text, Dock = DockStyle.Fill, FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Blue, Cursor = Cursors.Hand, Margin = new Padding(0, 0, 4, 4) }; button.FlatAppearance.BorderSize = 0; return button; } private void ConfigurePreviewGrid() { previewGrid.Dock = DockStyle.Fill; previewGrid.Margin = new Padding(7, 0, 0, 0); previewGrid.BackgroundColor = Color.White; previewGrid.BorderStyle = BorderStyle.None; previewGrid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal; previewGrid.GridColor = Color.FromArgb(228, 234, 242); previewGrid.RowHeadersVisible = false; previewGrid.ColumnHeadersHeight = 36; previewGrid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253); previewGrid.ColumnHeadersDefaultCellStyle.ForeColor = Navy; previewGrid.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI Semibold", 9F); previewGrid.EnableHeadersVisualStyles = false; previewGrid.DefaultCellStyle.BackColor = Color.White; previewGrid.DefaultCellStyle.ForeColor = Navy; previewGrid.DefaultCellStyle.SelectionBackColor = Color.White; previewGrid.DefaultCellStyle.SelectionForeColor = Navy; previewGrid.RowTemplate.Height = 28; previewGrid.AllowUserToAddRows = false; previewGrid.AllowUserToDeleteRows = false; previewGrid.AllowUserToOrderColumns = false; previewGrid.ReadOnly = true; previewGrid.MultiSelect = false; previewGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; } private void ReloadPreview(bool firstLoad) { try { loadedDocument = CsvCodec.Load(csvPath, headerCheckBox.Checked); if (loadedDocument.Headers.Count == 0) throw new InvalidDataException("Die Datei enthält keine auswertbaren CSV-Daten."); formatLabel.Text = loadedDocument.Rows.Count + " Zeilen • " + loadedDocument.Headers.Count + " Spalten • " + DelimiterName(loadedDocument.Delimiter); columnList.Items.Clear(); foreach (string header in loadedDocument.Headers) columnList.Items.Add(header, true); PopulatePreview(); UpdateSelectionStatus(); } catch (Exception exception) { if (!firstLoad) MessageBox.Show(this, "Die Vorschau konnte nicht aktualisiert werden.\r\n\r\n" + exception.Message, "Import-Assistent", MessageBoxButtons.OK, MessageBoxIcon.Error); importButton.Enabled = false; } } private void PopulatePreview() { previewGrid.SuspendLayout(); previewGrid.Columns.Clear(); previewGrid.Rows.Clear(); for (int column = 0; column < loadedDocument.Headers.Count; column++) { previewGrid.Columns.Add("Preview" + column, loadedDocument.Headers[column]); previewGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable; } foreach (var row in loadedDocument.Rows.Take(50)) previewGrid.Rows.Add(row.Cast().ToArray()); previewGrid.ClearSelection(); previewGrid.ResumeLayout(); } private void SetAllColumns(bool selected) { for (int index = 0; index < columnList.Items.Count; index++) columnList.SetItemChecked(index, selected); BeginInvoke(new Action(UpdateSelectionStatus)); } private void UpdateSelectionStatus() { int count = columnList.CheckedIndices.Count; selectionLabel.Text = count + " von " + columnList.Items.Count + " Spalten ausgewählt"; importButton.Enabled = count > 0; } private void FinishImport() { var selected = columnList.CheckedIndices.Cast().ToList(); if (selected.Count == 0) { MessageBox.Show(this, "Bitte wähle mindestens eine Spalte aus.", "Keine Spalte ausgewählt", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } SelectedDocument = CsvCodec.SelectColumns(loadedDocument, selected); DialogResult = DialogResult.OK; Close(); } private static string DelimiterName(char delimiter) { if (delimiter == ';') return "Semikolon"; if (delimiter == ',') return "Komma"; if (delimiter == '\t') return "Tabulator"; return delimiter.ToString(); } internal void UncheckLastColumnForPreview() { if (columnList.Items.Count > 1) columnList.SetItemChecked(columnList.Items.Count - 1, false); } } internal sealed class MainForm : Form { private readonly Color Navy = Color.FromArgb(24, 38, 58); private readonly Color Blue = Color.FromArgb(41, 112, 255); private readonly Color PaleBlue = Color.FromArgb(236, 243, 255); private readonly Color Background = Color.FromArgb(244, 247, 251); private readonly Color Muted = Color.FromArgb(94, 108, 128); private readonly DataGridView originalGrid = new DataGridView(); private readonly DataGridView resultGrid = new DataGridView(); private readonly ComboBox scopeComboBox = new ComboBox(); private readonly NumericUpDown stepNumeric = new NumericUpDown(); private readonly Label statusLabel = new Label(); private readonly Label fileLabel = new Label(); private readonly Button exportButton = new Button(); private readonly Button resetButton = new Button(); private readonly Button settingsButton = new Button(); private CsvDocument document; private string importedPath; private AppSettings appSettings = AppSettings.Load(); public MainForm() { Text = "PANDA – Pseudonymisierung alphanumerischer Nutzdaten"; StartPosition = FormStartPosition.CenterScreen; MinimumSize = new Size(1050, 680); Size = new Size(1320, 820); BackColor = Background; Font = new Font("Segoe UI", 9F); Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application; BuildLayout(); ConfigureGrid(originalGrid, true); ConfigureGrid(resultGrid, false); SetDocumentControlsEnabled(false); } private void BuildLayout() { var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 4, Padding = new Padding(20, 18, 20, 16), BackColor = Background }; root.RowStyles.Add(new RowStyle(SizeType.Absolute, 68)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 92)); root.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 40)); Controls.Add(root); var header = new Panel { Dock = DockStyle.Fill, BackColor = Background }; var title = new Label { Text = "PANDA", ForeColor = Navy, Font = new Font("Segoe UI Semibold", 19F), AutoSize = true, Location = new Point(0, 0) }; var subtitle = new Label { Text = "Pseudonymisierung alphanumerischer Nutzdaten durch Alphabetverschiebung", ForeColor = Muted, AutoSize = true, Location = new Point(2, 39) }; header.Controls.Add(title); header.Controls.Add(subtitle); root.Controls.Add(header, 0, 0); var toolbar = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 9, RowCount = 2, Padding = new Padding(12, 10, 12, 8), BackColor = Color.White, Margin = new Padding(0, 0, 0, 12) }; toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 148)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 160)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 76)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 135)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 135)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 108)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 122)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 116)); toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 34)); toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 28)); root.Controls.Add(toolbar, 0, 1); var importButton = CreateButton("Importieren", Blue, Color.White); importButton.Click += delegate { ImportCsv(); }; toolbar.Controls.Add(importButton, 0, 0); scopeComboBox.DropDownStyle = ComboBoxStyle.DropDownList; scopeComboBox.Items.AddRange(new object[] { "Markierte Zellen", "Aktuelle Zelle", "Alle Einträge" }); scopeComboBox.SelectedIndex = 0; scopeComboBox.Dock = DockStyle.Fill; scopeComboBox.Margin = new Padding(7, 3, 7, 3); toolbar.Controls.Add(scopeComboBox, 1, 0); stepNumeric.Minimum = 1; stepNumeric.Maximum = 25; stepNumeric.Value = Math.Max(1, Math.Min(25, appSettings.DefaultShift)); stepNumeric.Dock = DockStyle.Fill; stepNumeric.TextAlign = HorizontalAlignment.Center; stepNumeric.Margin = new Padding(7, 3, 7, 3); toolbar.Controls.Add(stepNumeric, 2, 0); var upButton = CreateButton("Hochzählen +", Color.FromArgb(29, 157, 105), Color.White); upButton.Click += delegate { ApplyShift((int)stepNumeric.Value); }; toolbar.Controls.Add(upButton, 3, 0); var downButton = CreateButton("Runterzählen −", Color.FromArgb(230, 91, 84), Color.White); downButton.Click += delegate { ApplyShift(-(int)stepNumeric.Value); }; toolbar.Controls.Add(downButton, 4, 0); resetButton.Text = "Zurücksetzen"; StyleSecondaryButton(resetButton); resetButton.Click += delegate { ResetResults(); }; toolbar.Controls.Add(resetButton, 5, 0); exportButton.Text = "CSV exportieren"; StyleSecondaryButton(exportButton); exportButton.Click += delegate { ExportCsv(); }; toolbar.Controls.Add(exportButton, 6, 0); settingsButton.Text = "Einstellungen"; StyleSecondaryButton(settingsButton); settingsButton.Click += delegate { OpenSettings(); }; toolbar.Controls.Add(settingsButton, 7, 0); fileLabel.Text = "Noch keine CSV geladen"; fileLabel.ForeColor = Muted; fileLabel.AutoEllipsis = true; fileLabel.Dock = DockStyle.Fill; fileLabel.TextAlign = ContentAlignment.MiddleLeft; toolbar.SetColumnSpan(fileLabel, 4); toolbar.Controls.Add(fileLabel, 0, 1); var hint = new Label { Text = "Tipp: Strg oder Umschalt gedrückt halten, um mehrere Zellen zu markieren.", ForeColor = Muted, AutoEllipsis = true, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleRight }; toolbar.SetColumnSpan(hint, 5); toolbar.Controls.Add(hint, 4, 1); var grids = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 2, BackColor = Background, Margin = new Padding(0) }; grids.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); grids.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); grids.RowStyles.Add(new RowStyle(SizeType.Absolute, 42)); grids.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); root.Controls.Add(grids, 0, 2); grids.Controls.Add(CreateGridHeader("ORIGINAL", "Importierte CSV-Werte", false), 0, 0); grids.Controls.Add(CreateGridHeader("ERGEBNIS", "Veränderte Werte", true), 1, 0); originalGrid.Margin = new Padding(0, 0, 7, 0); resultGrid.Margin = new Padding(7, 0, 0, 0); grids.Controls.Add(originalGrid, 0, 1); grids.Controls.Add(resultGrid, 1, 1); var statusPanel = new Panel { Dock = DockStyle.Fill, BackColor = Background }; statusLabel.Text = "Bereit – bitte eine CSV-Datei importieren."; statusLabel.ForeColor = Muted; statusLabel.AutoSize = true; statusLabel.Location = new Point(2, 12); statusPanel.Controls.Add(statusLabel); root.Controls.Add(statusPanel, 0, 3); } private Panel CreateGridHeader(string eyebrow, string caption, bool result) { var panel = new Panel { Dock = DockStyle.Fill, BackColor = result ? PaleBlue : Color.White, Margin = result ? new Padding(7, 0, 0, 0) : new Padding(0, 0, 7, 0) }; var title = new Label { Text = eyebrow + " " + caption, Font = new Font("Segoe UI Semibold", 10F), ForeColor = result ? Blue : Navy, AutoSize = true, Location = new Point(12, 11) }; panel.Controls.Add(title); return panel; } private Button CreateButton(string text, Color background, Color foreground) { var button = new Button { Text = text, Dock = DockStyle.Fill, FlatStyle = FlatStyle.Flat, BackColor = background, ForeColor = foreground, Cursor = Cursors.Hand, Margin = new Padding(4, 2, 4, 2) }; button.FlatAppearance.BorderSize = 0; return button; } private void StyleSecondaryButton(Button button) { button.Dock = DockStyle.Fill; button.FlatStyle = FlatStyle.Flat; button.BackColor = Color.White; button.ForeColor = Navy; button.Cursor = Cursors.Hand; button.Margin = new Padding(4, 2, 4, 2); button.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230); } private void OpenSettings() { using (var dialog = new SettingsForm(appSettings)) { if (dialog.ShowDialog(this) != DialogResult.OK) return; appSettings.DefaultShift = dialog.DefaultShift; appSettings.ConfirmBeforeShift = dialog.ConfirmBeforeShift; try { appSettings.Save(); stepNumeric.Value = appSettings.DefaultShift; statusLabel.Text = "Einstellungen gespeichert. Standard-Zählwert: " + appSettings.DefaultShift + "."; statusLabel.ForeColor = Color.FromArgb(29, 132, 88); } catch (Exception exception) { MessageBox.Show(this, "Die Einstellungen konnten nicht gespeichert werden.\r\n\r\n" + exception.Message, "Speichern fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void ConfigureGrid(DataGridView grid, bool selectable) { grid.Dock = DockStyle.Fill; grid.BackgroundColor = Color.White; grid.BorderStyle = BorderStyle.None; grid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal; grid.GridColor = Color.FromArgb(228, 234, 242); grid.RowHeadersVisible = true; grid.RowHeadersWidth = selectable ? 74 : 50; grid.RowHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253); grid.RowHeadersDefaultCellStyle.ForeColor = Muted; grid.RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; grid.ColumnHeadersHeight = 36; grid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; grid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253); grid.ColumnHeadersDefaultCellStyle.ForeColor = Navy; grid.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI Semibold", 9F); grid.ColumnHeadersDefaultCellStyle.SelectionBackColor = Color.FromArgb(248, 250, 253); grid.EnableHeadersVisualStyles = false; grid.DefaultCellStyle.BackColor = Color.White; grid.DefaultCellStyle.ForeColor = Navy; grid.DefaultCellStyle.SelectionBackColor = selectable ? Color.FromArgb(214, 227, 255) : Color.White; grid.DefaultCellStyle.SelectionForeColor = Navy; grid.DefaultCellStyle.Padding = new Padding(4, 2, 4, 2); grid.RowTemplate.Height = 30; grid.AllowUserToAddRows = false; grid.AllowUserToDeleteRows = false; grid.AllowUserToOrderColumns = false; grid.AllowUserToResizeRows = false; grid.MultiSelect = true; grid.SelectionMode = DataGridViewSelectionMode.CellSelect; grid.ReadOnly = true; grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; grid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText; grid.DataError += delegate { }; if (selectable) { grid.RowPostPaint += DrawOriginalRowHeader; grid.RowHeaderMouseClick += ToggleOriginalRow; grid.Scroll += delegate { SyncScroll(originalGrid, resultGrid); }; } else { grid.MultiSelect = false; grid.TabStop = false; grid.SelectionChanged += delegate { if (grid.SelectedCells.Count > 0) grid.ClearSelection(); }; grid.CellMouseDown += delegate(object sender, DataGridViewCellMouseEventArgs args) { if (args.RowIndex < 0 || args.ColumnIndex < 0) return; originalGrid.ClearSelection(); originalGrid.CurrentCell = originalGrid.Rows[args.RowIndex].Cells[args.ColumnIndex]; originalGrid.CurrentCell.Selected = true; originalGrid.Focus(); BeginInvoke(new Action(delegate { grid.ClearSelection(); grid.CurrentCell = null; })); }; grid.Scroll += delegate { SyncScroll(resultGrid, originalGrid); }; } } private void DrawOriginalRowHeader(object sender, DataGridViewRowPostPaintEventArgs args) { var grid = (DataGridView)sender; var bounds = new Rectangle(0, args.RowBounds.Top, grid.RowHeadersWidth, args.RowBounds.Height); using (var backgroundBrush = new SolidBrush(Color.FromArgb(248, 250, 253))) args.Graphics.FillRectangle(backgroundBrush, bounds); using (var linePen = new Pen(Color.FromArgb(228, 234, 242))) args.Graphics.DrawLine(linePen, bounds.Left, bounds.Bottom - 1, bounds.Right, bounds.Bottom - 1); bool selected = grid.Rows[args.RowIndex].Tag is bool && (bool)grid.Rows[args.RowIndex].Tag; int checkSize = 14; var checkPoint = new Point(8, bounds.Top + (bounds.Height - checkSize) / 2); CheckBoxRenderer.DrawCheckBox(args.Graphics, checkPoint, selected ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal); var numberBounds = new Rectangle(29, bounds.Top, bounds.Width - 32, bounds.Height); TextRenderer.DrawText(args.Graphics, (args.RowIndex + 1).ToString(), grid.Font, numberBounds, Muted, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.NoPadding); } private void ToggleOriginalRow(object sender, DataGridViewCellMouseEventArgs args) { if (args.RowIndex < 0 || args.RowIndex >= originalGrid.Rows.Count) return; var row = originalGrid.Rows[args.RowIndex]; bool wasSelected = row.Tag is bool && (bool)row.Tag; bool isSelected = !wasSelected; row.Tag = isSelected; foreach (DataGridViewCell cell in row.Cells) cell.Selected = isSelected; originalGrid.InvalidateRow(args.RowIndex); BeginInvoke(new Action(RefreshCheckedRowSelections)); statusLabel.Text = isSelected ? "Zeile " + (args.RowIndex + 1) + " vollständig ausgewählt." : "Zeile " + (args.RowIndex + 1) + " aus der vollständigen Auswahl entfernt."; statusLabel.ForeColor = Muted; } private void RefreshCheckedRowSelections() { foreach (DataGridViewRow row in originalGrid.Rows) { bool isSelected = row.Tag is bool && (bool)row.Tag; if (!isSelected) continue; foreach (DataGridViewCell cell in row.Cells) cell.Selected = true; } originalGrid.Invalidate(); } private void ImportCsv() { using (var dialog = new OpenFileDialog()) { dialog.Title = "CSV-Datei importieren"; dialog.Filter = "CSV-Dateien (*.csv)|*.csv|Textdateien (*.txt)|*.txt|Alle Dateien (*.*)|*.*"; dialog.CheckFileExists = true; dialog.Multiselect = false; if (dialog.ShowDialog(this) != DialogResult.OK) return; try { using (var wizard = new ImportWizardForm(dialog.FileName)) { if (wizard.ShowDialog(this) != DialogResult.OK) return; document = wizard.SelectedDocument; } importedPath = dialog.FileName; PopulateGrids(); fileLabel.Text = Path.GetFileName(dialog.FileName) + " • " + document.Rows.Count + " Zeilen • " + document.Headers.Count + " importierte Spalten • " + DelimiterName(document.Delimiter); statusLabel.Text = "Import erfolgreich. Wähle links Zellen aus oder nutze ‚Alle Einträge‘."; statusLabel.ForeColor = Color.FromArgb(29, 132, 88); SetDocumentControlsEnabled(true); } catch (Exception exception) { MessageBox.Show(this, "Die CSV-Datei konnte nicht importiert werden.\r\n\r\n" + exception.Message, "Import fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error); statusLabel.Text = "Import fehlgeschlagen."; statusLabel.ForeColor = Color.FromArgb(190, 60, 55); } } } private string DelimiterName(char delimiter) { if (delimiter == ';') return "Semikolon"; if (delimiter == ',') return "Komma"; if (delimiter == '\t') return "Tabulator"; return delimiter.ToString(); } private void PopulateGrids() { originalGrid.SuspendLayout(); resultGrid.SuspendLayout(); originalGrid.Columns.Clear(); resultGrid.Columns.Clear(); originalGrid.Rows.Clear(); resultGrid.Rows.Clear(); for (int column = 0; column < document.Headers.Count; column++) { string key = "Column" + column; originalGrid.Columns.Add(key, document.Headers[column]); resultGrid.Columns.Add(key, document.Headers[column]); originalGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable; resultGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable; } foreach (var row in document.Rows) { originalGrid.Rows.Add(row.Cast().ToArray()); resultGrid.Rows.Add(row.Cast().ToArray()); } for (int row = 0; row < document.Rows.Count; row++) { originalGrid.Rows[row].HeaderCell.Value = (row + 1).ToString(); originalGrid.Rows[row].Tag = false; resultGrid.Rows[row].HeaderCell.Value = (row + 1).ToString(); } originalGrid.ClearSelection(); resultGrid.ClearSelection(); resultGrid.CurrentCell = null; originalGrid.ResumeLayout(); resultGrid.ResumeLayout(); } private void ApplyShift(int amount) { if (document == null) return; var cells = GetTargetCells(); if (cells.Count == 0) { MessageBox.Show(this, "Bitte markiere mindestens eine Zelle in der linken Tabelle oder wähle als Bereich ‚Alle Einträge‘.", "Keine Auswahl", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (appSettings.ConfirmBeforeShift) { string message = BuildConfirmationMessage(amount, cells.Count, scopeComboBox.SelectedIndex == 2); DialogResult confirmation = MessageBox.Show(this, message, "Umwandlung bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); if (confirmation != DialogResult.Yes) { statusLabel.Text = "Umwandlung abgebrochen."; statusLabel.ForeColor = Muted; return; } } int changed = 0; foreach (var coordinate in cells) { var cell = resultGrid.Rows[coordinate.Item1].Cells[coordinate.Item2]; string before = Convert.ToString(cell.Value) ?? string.Empty; string after = LetterShifter.Shift(before, amount); cell.Value = after; if (!string.Equals(before, after, StringComparison.Ordinal)) changed++; } statusLabel.Text = changed + " von " + cells.Count + " Zellen verändert (" + (amount > 0 ? "+" : string.Empty) + amount + ")."; statusLabel.ForeColor = Color.FromArgb(29, 132, 88); } internal static string BuildConfirmationMessage(int amount, int cellCount, bool allValues) { string subject = allValues ? "Alle Werte" : "Die ausgewählten Werte"; string direction = amount > 0 ? "hochgezählt" : "runtergezählt"; return subject + " werden um " + Math.Abs(amount) + " " + direction + ".\r\n\r\n" + "Gewählter Zählwert: " + Math.Abs(amount) + "\r\n" + "Betroffene Zellen: " + cellCount + "\r\n\r\n" + "Möchtest du die Umwandlung durchführen?"; } private List> GetTargetCells() { var result = new List>(); if (scopeComboBox.SelectedIndex == 2) { for (int row = 0; row < resultGrid.Rows.Count; row++) for (int column = 0; column < resultGrid.Columns.Count; column++) result.Add(Tuple.Create(row, column)); return result; } if (scopeComboBox.SelectedIndex == 1) { var current = originalGrid.CurrentCell; if (current != null) result.Add(Tuple.Create(current.RowIndex, current.ColumnIndex)); return result; } foreach (DataGridViewCell cell in originalGrid.SelectedCells) result.Add(Tuple.Create(cell.RowIndex, cell.ColumnIndex)); foreach (DataGridViewRow row in originalGrid.Rows) { bool wholeRow = row.Tag is bool && (bool)row.Tag; if (!wholeRow) continue; for (int column = 0; column < resultGrid.Columns.Count; column++) result.Add(Tuple.Create(row.Index, column)); } return result.Distinct().ToList(); } private void ResetResults() { if (document == null) return; for (int row = 0; row < document.Rows.Count; row++) for (int column = 0; column < document.Headers.Count; column++) resultGrid.Rows[row].Cells[column].Value = document.Rows[row][column]; statusLabel.Text = "Alle Ergebnisse wurden auf die importierten Werte zurückgesetzt."; statusLabel.ForeColor = Muted; } private void ExportCsv() { if (document == null) return; using (var dialog = new SaveFileDialog()) { dialog.Title = "Veränderte CSV exportieren"; dialog.Filter = "CSV-Dateien (*.csv)|*.csv|Alle Dateien (*.*)|*.*"; dialog.DefaultExt = "csv"; dialog.AddExtension = true; string sourceName = string.IsNullOrEmpty(importedPath) ? "ergebnis" : Path.GetFileNameWithoutExtension(importedPath) + "_veraendert"; dialog.FileName = sourceName + ".csv"; dialog.InitialDirectory = string.IsNullOrEmpty(importedPath) ? Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) : Path.GetDirectoryName(importedPath); if (dialog.ShowDialog(this) != DialogResult.OK) return; try { var rows = new List>(); foreach (DataGridViewRow gridRow in resultGrid.Rows) { var values = new List(); foreach (DataGridViewCell cell in gridRow.Cells) values.Add(Convert.ToString(cell.Value) ?? string.Empty); rows.Add(values); } CsvCodec.Save(dialog.FileName, document, rows); statusLabel.Text = "Export erfolgreich: " + dialog.FileName; statusLabel.ForeColor = Color.FromArgb(29, 132, 88); MessageBox.Show(this, "Die veränderte CSV wurde erfolgreich gespeichert.", "Export abgeschlossen", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception exception) { MessageBox.Show(this, "Die CSV-Datei konnte nicht gespeichert werden.\r\n\r\n" + exception.Message, "Export fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void SyncScroll(DataGridView source, DataGridView target) { if (source.Rows.Count == 0 || target.Rows.Count == 0) return; try { target.FirstDisplayedScrollingRowIndex = source.FirstDisplayedScrollingRowIndex; target.HorizontalScrollingOffset = source.HorizontalScrollingOffset; } catch (ArgumentOutOfRangeException) { } } private void SetDocumentControlsEnabled(bool enabled) { scopeComboBox.Enabled = enabled; if (enabled && scopeComboBox.SelectedIndex < 0 && scopeComboBox.Items.Count > 0) scopeComboBox.SelectedIndex = 0; stepNumeric.Enabled = enabled; exportButton.Enabled = enabled; resetButton.Enabled = enabled; scopeComboBox.Refresh(); } internal void LoadPreviewData() { document = new CsvDocument { Delimiter = ';', FirstRowIsHeader = true }; document.Headers.AddRange(new[] { "Kundennummer", "Vorname", "Nachname", "Ort" }); document.Rows.Add(new List { "1001", "Anna", "Meyer", "Berlin" }); document.Rows.Add(new List { "1002", "Jonas", "Schmidt", "Hamburg" }); document.Rows.Add(new List { "1003", "Zoe", "Fischer", "München" }); document.Rows.Add(new List { "1004", "Lena", "Wagner", "Köln" }); PopulateGrids(); for (int column = 0; column < document.Headers.Count; column++) resultGrid.Rows[0].Cells[column].Value = LetterShifter.Shift(document.Rows[0][column], 1); originalGrid.Rows[0].Tag = true; RefreshCheckedRowSelections(); fileLabel.Text = "beispiel.csv • 4 Zeilen • 4 Spalten • Trennzeichen: Semikolon"; statusLabel.Text = "3 von 4 Zellen verändert (+1)."; statusLabel.ForeColor = Color.FromArgb(29, 132, 88); SetDocumentControlsEnabled(true); scopeComboBox.SelectedIndex = 0; scopeComboBox.Refresh(); } } }