нужно в окне с графиками  реализовать
1. возможность пометить точки, которые должны подсвечиваться
2. возможность удалить эти точки
3. справа нужно добавить место где отобразиться файл который использовался для построения графика на котором происходит редактирование точек
4. когда точка выделена на графике она выделяется в файле справа
5. добавить undo redo
6. возмождность применить изменения и сохранение в окне mainwindow рядом с остальн7ыми файлами
7. сделать графики txt файлов в виде точек без линии
8. нужно добавить для графиков по magnete настройку масштаба, там значения x и y в метрах, следовательно нужно сделать выбор масштаба например 1:1 1:10 1:100 и так далее



<!-- MainWindow.xaml -->
<Window x:Class="TxtProcessorApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TxtProcessorApp"
        Title="Txt Processor" Height="800" Width="1200">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <!-- Панель кнопок -->
            <RowDefinition Height="*"/>
            <!-- Основная область с данными -->
            <RowDefinition Height="Auto"/>
            <!-- Строка состояния -->
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <!-- Столбец для .txt -->
            <ColumnDefinition Width="20"/>
            <!-- Разделитель -->
            <ColumnDefinition Width="*"/>
            <!-- Столбец для .magnete -->
        </Grid.ColumnDefinitions>

        <!-- Верхняя панель кнопок загрузки и управления файлами -->
        <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Left" Margin="0,0,0,10">
            <Button Content="Загрузить .txt файлы" Width="150" Height="30" Click="LoadTxtFiles_Click"/>
            <Button Content="Загрузить .magnete файлы" Width="180" Height="30" Click="LoadMagneteFiles_Click" Margin="10,0,0,0"/>
            <!-- Кнопка "Проект" с выпадающим списком -->
            <Button Content="Проект" Width="120" Height="30" Margin="10,0,0,0" Click="ProjectButton_Click">
                <Button.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Сохранить проект" Click="SaveProject_Click"/>
                        <MenuItem Header="Загрузить проект" Click="LoadProject_Click"/>
                    </ContextMenu>
                </Button.ContextMenu>
            </Button>
            <Button Content="Сформировать файл" Width="150" Height="30" Click="CombineFiles_Click" Margin="10,0,0,0"/>
            <Button Content="Редактировать и Экспортировать" Width="220" Height="30" Click="EditAndExport_Click" IsEnabled="False" x:Name="EditExportButton" Margin="10,0,0,0"/>
            <Button Content="Графики" Width="100" Height="30" Click="ShowGraphs_Click" Margin="10,0,0,0"/>
        </StackPanel>

        <!-- Разделитель между столбцами -->
        <Separator Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5,0"/>

        <!-- Левая область для .txt файлов -->
        <GroupBox Header=".txt Файлы" Grid.Row="1" Grid.Column="0" Margin="0,10,10,10">
            <TabControl x:Name="TxtFilesTabControl" Margin="5"/>
        </GroupBox>

        <!-- Правая область для .magnete файлов -->
        <GroupBox Header=".magnete Файлы" Grid.Row="1" Grid.Column="2" Margin="10,10,0,10">
            <TabControl x:Name="MagneteFilesTabControl" Margin="5"/>
        </GroupBox>

        <!-- Строка состояния -->
        <StatusBar Grid.Row="2" Grid.ColumnSpan="3">
            <StatusBarItem>
                <TextBlock x:Name="StatusTextBlock" Text="Готово"/>
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>

// MainWindow.xaml.cs
using Microsoft.Win32;
using ProjNet.CoordinateSystems.Transformations;
using ProjNet.CoordinateSystems;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Newtonsoft.Json;

namespace TxtProcessorApp
{
    public partial class MainWindow : Window
    {
        // Словари для хранения данных из файлов
        private Dictionary<string, List<string[]>> txtFileData = new Dictionary<string, List<string[]>>();
        private Dictionary<string, List<string[]>> magneteFileData = new Dictionary<string, List<string[]>>();

        // Хранение объединённых данных для дальнейшего редактирования
        private DataTable combinedTxtDataTable = null;
        private DataTable combinedMagneteDataTable = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        #region Обработчики загрузки файлов .txt

        // Обработчик кнопки загрузки .txt файлов
        private void LoadTxtFiles_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                Multiselect = true,
                Filter = "Текстовые файлы (*.txt)|*.txt"
            };

            if (ofd.ShowDialog() == true)
            {
                foreach (var file in ofd.FileNames)
                {
                    ProcessTxtFile(file);
                }

                // Обновляем TabControl с вкладками для каждого .txt файла
                PopulateTxtTabControl();

                // Деактивируем кнопку редактирования до объединения
                EditExportButton.IsEnabled = false;
                combinedTxtDataTable = null;

                StatusTextBlock.Text = $"Загружено {txtFileData.Count} .txt файлов.";
            }
        }

        // Метод для обработки отдельного .txt файла
        private void ProcessTxtFile(string filePath)
        {
            List<string[]> processedLines = new List<string[]>();

            try
            {
                var lines = File.ReadAllLines(filePath).Skip(11); // Пропуск первых 11 строк

                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line))
                        continue;

                    var columns = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (columns.Length == 0)
                        continue;

                    // Проверка первого столбца
                    if (columns[0].ToLower() == "nan" || string.IsNullOrWhiteSpace(columns[0]))
                        continue;

                    // Создаем список для новых столбцов
                    List<string> newColumns = new List<string>();

                    // Добавляем столбцы, кроме первого
                    for (int i = 1; i < columns.Length; i++)
                    {
                        newColumns.Add(columns[i]);
                    }

                    processedLines.Add(newColumns.ToArray());
                }

                // Добавляем обработанные данные в словарь
                string fileName = System.IO.Path.GetFileName(filePath);

                // Проверяем, если файл уже загружен, добавляем суффикс
                string originalFileName = fileName;
                int duplicateCount = 1;
                while (txtFileData.ContainsKey(fileName))
                {
                    fileName = $"{System.IO.Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{System.IO.Path.GetExtension(originalFileName)}";
                    duplicateCount++;
                }

                txtFileData[fileName] = processedLines;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для заполнения TxtFilesTabControl вкладками для каждого .txt файла
        private void PopulateTxtTabControl()
        {
            TxtFilesTabControl.Items.Clear();

            foreach (var kvp in txtFileData)
            {
                string fileName = kvp.Key;
                List<string[]> data = kvp.Value;

                // Создаем DataGrid для текущего файла
                DataGrid dataGrid = CreateTxtDataGrid(data);

                // Создаем вкладку
                TabItem tabItem = new TabItem
                {
                    Header = fileName,
                    Content = dataGrid
                };

                TxtFilesTabControl.Items.Add(tabItem);
            }
        }

        private DataGrid CreateTxtDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов вручную
            dataGrid.Columns.Clear();

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "№",
                Binding = new Binding("Number"),
                Width = new DataGridLength(50, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "BASE-1",
                Binding = new Binding("Base1"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Noise",
                Binding = new Binding("Noise"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time",
                Binding = new Binding("Time"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time UTC",
                Binding = new Binding("TimeUTC"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // Добавление стилей для ячеек и заголовков столбцов
            Style cellStyle = new Style(typeof(DataGridCell));
            cellStyle.Setters.Add(new Setter(DataGridCell.PaddingProperty, new Thickness(5)));
            cellStyle.Setters.Add(new Setter(DataGridCell.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));

            Style headerStyle = new Style(typeof(DataGridColumnHeader));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.FontWeightProperty, FontWeights.Bold));

            dataGrid.CellStyle = cellStyle;
            dataGrid.ColumnHeaderStyle = headerStyle;

            // Преобразуем данные в DataTable
            DataTable dataTable = ConvertToDataTable(data);
            dataGrid.ItemsSource = dataTable.DefaultView;

            return dataGrid;
        }

        // Метод для преобразования списка строк .txt в DataTable
        private DataTable ConvertToDataTable(List<string[]> data)
        {
            DataTable dt = new DataTable();

            if (data == null || data.Count == 0)
                return dt;

            // Создание столбцов с заданными названиями
            dt.Columns.Add("Number", typeof(int));
            dt.Columns.Add("Base1", typeof(string));
            dt.Columns.Add("Noise", typeof(string));
            dt.Columns.Add("Time", typeof(string));
            dt.Columns.Add("TimeUTC", typeof(string));

            int number = 1;

            // Добавление строк
            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                dr["Number"] = number++;
                dr["Base1"] = row.Length > 0 ? row[0] : string.Empty;
                dr["Noise"] = row.Length > 1 ? row[1] : string.Empty;
                dr["Time"] = row.Length > 2 ? row[2] : string.Empty;
                dr["TimeUTC"] = row.Length > 3 ? row[3] : string.Empty;
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчики загрузки файлов .magnete

        // Обработчик кнопки загрузки .magnete файлов
        private void LoadMagneteFiles_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                Multiselect = true,
                Filter = "Magnete файлы (*.magnete)|*.magnete"
            };

            if (ofd.ShowDialog() == true)
            {
                foreach (var file in ofd.FileNames)
                {
                    ProcessMagneteFile(file);
                }

                // Обновляем TabControl с вкладками для каждого .magnete файла
                PopulateMagneteTabControl();

                // Деактивируем кнопку редактирования до объединения
                EditExportButton.IsEnabled = false;
                combinedMagneteDataTable = null;

                StatusTextBlock.Text = $"Загружено {magneteFileData.Count} .magnete файлов.";
            }
        }

        // Метод для обработки отдельного .magnete файла
        private void ProcessMagneteFile(string filePath)
        {
            List<string[]> processedLines = new List<string[]>();

            try
            {
                var lines = File.ReadAllLines(filePath); // Удалено .Skip(11) для обработки всех строк

                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line))
                        continue;

                    var columns = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (columns.Length == 0)
                        continue;

                    // Проверка первого столбца
                    if (columns[0].ToLower() == "nan" || string.IsNullOrWhiteSpace(columns[0]))
                        continue;

                    // Добавляем все столбцы
                    processedLines.Add(columns);
                }

                // Добавляем обработанные данные в словарь
                string fileName = System.IO.Path.GetFileName(filePath);

                // Проверяем, если файл уже загружен, добавляем суффикс
                string originalFileName = fileName;
                int duplicateCount = 1;
                while (magneteFileData.ContainsKey(fileName))
                {
                    fileName = $"{System.IO.Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{System.IO.Path.GetExtension(originalFileName)}";
                    duplicateCount++;
                }

                magneteFileData[fileName] = processedLines;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для заполнения MagneteFilesTabControl вкладками для каждого .magnete файла
        private void PopulateMagneteTabControl()
        {
            MagneteFilesTabControl.Items.Clear();

            foreach (var kvp in magneteFileData)
            {
                string fileName = kvp.Key;
                List<string[]> data = kvp.Value;

                // Создаем DataGrid для текущего файла
                DataGrid dataGrid = CreateMagneteDataGrid(data);

                // Создаем вкладку
                TabItem tabItem = new TabItem
                {
                    Header = fileName,
                    Content = dataGrid
                };

                MagneteFilesTabControl.Items.Add(tabItem);
            }
        }

        private DataGrid CreateMagneteDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов вручную
            dataGrid.Columns.Clear();

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Stan",
                Binding = new Binding("Stan"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Zeit",
                Binding = new Binding("Zeit"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Noise",
                Binding = new Binding("Noise"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time",
                Binding = new Binding("Time"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "TimeUTC",
                Binding = new Binding("TimeUTC"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // Добавление стилей для ячеек и заголовков столбцов
            Style cellStyle = new Style(typeof(DataGridCell));
            cellStyle.Setters.Add(new Setter(DataGridCell.PaddingProperty, new Thickness(5)));
            cellStyle.Setters.Add(new Setter(DataGridCell.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));

            Style headerStyle = new Style(typeof(DataGridColumnHeader));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.FontWeightProperty, FontWeights.Bold));

            dataGrid.CellStyle = cellStyle;
            dataGrid.ColumnHeaderStyle = headerStyle;

            // Преобразуем данные в DataTable
            DataTable dataTable = ConvertMagneteToDataTable(data);
            dataGrid.ItemsSource = dataTable.DefaultView;

            return dataGrid;
        }

        private DataTable ConvertMagneteToDataTable(List<string[]> data)
        {
            DataTable dt = new DataTable();

            // Определение столбцов (поменяйте согласно вашим требованиям)
            dt.Columns.Add("Stan", typeof(string));
            dt.Columns.Add("Zeit", typeof(string));
            dt.Columns.Add("Noise", typeof(string));
            dt.Columns.Add("Time", typeof(string));
            dt.Columns.Add("TimeUTC", typeof(string));

            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                dr["Stan"] = row.Length > 0 ? row[0].Trim() : string.Empty;
                dr["Zeit"] = row.Length > 1 ? row[1].Trim() : string.Empty;
                dr["Noise"] = row.Length > 2 ? row[2].Trim() : string.Empty;
                dr["Time"] = row.Length > 3 ? row[3].Trim() : string.Empty;
                dr["TimeUTC"] = row.Length > 4 ? row[4].Trim() : string.Empty;
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчик объединения файлов

        // Обработчик кнопки Объединить файлы
        private void CombineFiles_Click(object sender, RoutedEventArgs e)
        {
            if (!txtFileData.Any() && !magneteFileData.Any())
            {
                MessageBox.Show("Нет загруженных файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Диалоговое окно для выбора типа файлов для объединения
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите тип файлов для объединения:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && txtFileData.Any())
                {
                    CombineTxtFiles();
                }
                else if (selectedOption == ".magnete файлы" && magneteFileData.Any())
                {
                    CombineMagneteFiles();
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или нет загруженных файлов данного типа.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Метод объединения .txt файлов
        private void CombineTxtFiles()
        {
            List<string[]> combinedData = new List<string[]>();
            int lineNumber = 1;

            foreach (var file in txtFileData.Values)
            {
                foreach (var row in file)
                {
                    if (row.Length < 3)
                        continue; // Убедитесь, что есть минимум 3 столбца

                    // Извлечение необходимых столбцов
                    string secondColumn = row[1];
                    string thirdColumn = row.Length > 1 ? row[2] : string.Empty;
                    string fourthColumn = row.Length > 2 ? row[3] : "0"; // Если нет 4-го столбца, используем "0"

                    // Обработка пятого столбца
                    if (double.TryParse(fourthColumn, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
                    {
                        double divided = number / 24;
                        TimeSpan time = TimeSpan.FromDays(divided);
                        string timeString = time.ToString(@"hh\:mm\:ss");
                        combinedData.Add(new string[]
                        {
                            lineNumber.ToString(),
                            secondColumn,
                            thirdColumn,
                            fourthColumn,
                            timeString
                        });
                    }
                    else
                    {
                        combinedData.Add(new string[]
                        {
                            lineNumber.ToString(),
                            secondColumn,
                            thirdColumn,
                            fourthColumn,
                            "InvalidNumber"
                        });
                    }

                    lineNumber++;
                }
            }

            // Преобразуем объединённые данные в DataTable с нужными названиями столбцов
            combinedTxtDataTable = ConvertToCombinedDataTable(combinedData, isTxt: true);

            // Добавляем вкладку с объединёнными данными
            AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", TxtFilesTabControl);

            // Активируем кнопку редактирования
            EditExportButton.IsEnabled = true;

            StatusTextBlock.Text = $"Объединены .txt файлы. Строк: {combinedTxtDataTable.Rows.Count}.";
        }

        // Метод объединения .magnete файлов с сохранением оригинальной логики
        private void CombineMagneteFiles()
        {
            if (!magneteFileData.Any())
            {
                MessageBox.Show("Нет загруженных .magnete файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Фиксированная дата (например, 06.01.1980) в UTC
            DateTime fixedDate = new DateTime(1980, 1, 6, 0, 0, 0, DateTimeKind.Utc);

            // Объединяем все данные из .magnete файлов
            combinedMagneteDataTable = new DataTable();

            // Определение столбцов с уникальными внутренними именами (согласно оригиналу)
            combinedMagneteDataTable.Columns.Add("DATA", typeof(string));
            combinedMagneteDataTable.Columns.Add("DATA UTC", typeof(string));
            combinedMagneteDataTable.Columns.Add("TIME", typeof(string));
            combinedMagneteDataTable.Columns.Add("TIME UTC", typeof(string));
            combinedMagneteDataTable.Columns.Add("Долгота WGS", typeof(string)); // раньше "Noise"
            combinedMagneteDataTable.Columns.Add("Широта WGS", typeof(string));  // раньше "Zeit"
            combinedMagneteDataTable.Columns.Add("Долгота UTM", typeof(double)); // раньше "Zero1"
            combinedMagneteDataTable.Columns.Add("Широта UTM", typeof(double));  // раньше "Zero2"
            combinedMagneteDataTable.Columns.Add("№1", typeof(int));             // раньше "SequentialNumber1"
            combinedMagneteDataTable.Columns.Add("Альтитуда", typeof(string));    // раньше "Time"
            combinedMagneteDataTable.Columns.Add("№2", typeof(int));             // раньше "SequentialNumber2"
            combinedMagneteDataTable.Columns.Add("Shark", typeof(string));        // раньше "TimeUTC"

            int sequentialNumber = 1;
            List<string> errorMessages = new List<string>();

            foreach (var file in magneteFileData.Values)
            {
                foreach (var row in file)
                {
                    // Создание новой строки для объединённой таблицы
                    DataRow newRow = combinedMagneteDataTable.NewRow();

                    // Столбцы "DATA UTC"
                    newRow["DATA UTC"] = fixedDate.ToString("dd.MM.yyyy");

                    // Столбцы "TIME" и "TIME UTC"
                    string stanValue = row.Length > 0 ? row[0].Trim() : string.Empty;
                    newRow["TIME"] = stanValue;

                    // Столбцы "Долгота WGS" и "Широта WGS"
                    string zeitValue = row.Length > 1 ? row[1].Trim() : string.Empty;
                    newRow["Широта WGS"] = zeitValue;

                    string noiseValue = row.Length > 2 ? row[2].Trim() : string.Empty;
                    newRow["Долгота WGS"] = noiseValue;

                    // Столбцы "№1" и "№2"
                    newRow["№1"] = sequentialNumber; // SequentialNumber1
                    newRow["№2"] = sequentialNumber; // SequentialNumber2

                    // Столбцы "Альтитуда" и "Shark"
                    string timeValueStr = row.Length > 3 ? row[3].Trim() : string.Empty;
                    newRow["Альтитуда"] = timeValueStr;

                    string timeUtcValue = row.Length > 4 ? row[4].Trim() : string.Empty;
                    newRow["Shark"] = timeUtcValue;

                    // Столбцы "Долгота UTM" и "Широта UTM" будут заполнены позже
                    newRow["Долгота UTM"] = 0.0; // Изначально 0.0, потом заменим
                    newRow["Широта UTM"] = 0.0;

                    // Для логирования
                    string computedDateStr = "N/A";
                    string computedTimeStr = "N/A"; // Объявлено

                    // Преобразование координат из "Долгота WGS" и "Широта WGS" в UTM
                    double longitude, latitude;
                    bool validCoordinates = true;

                    if (!double.TryParse(noiseValue, NumberStyles.Any, CultureInfo.InvariantCulture, out longitude))
                    {
                        string error = $"Не удалось распарсить Долгота WGS='{noiseValue}' как долготу в строке: {string.Join("\t", row)}.";
                        errorMessages.Add(error);
                        LogCalculation(error);
                        validCoordinates = false;
                    }

                    if (!double.TryParse(zeitValue, NumberStyles.Any, CultureInfo.InvariantCulture, out latitude))
                    {
                        string error = $"Не удалось распарсить Широта WGS='{zeitValue}' как широту в строке: {string.Join("\t", row)}.";
                        errorMessages.Add(error);
                        LogCalculation(error);
                        validCoordinates = false;
                    }

                    if (validCoordinates)
                    {
                        try
                        {
                            // Определяем UTM-зону
                            int utmZone = GetUTMZone(longitude);

                            // Преобразуем координаты в UTM
                            var utmCoordinates = ConvertWGS84ToUTM(longitude, latitude, utmZone);

                            // Записываем UTM координаты в "Долгота UTM" и "Широта UTM"
                            newRow["Долгота UTM"] = utmCoordinates.Easting;
                            newRow["Широта UTM"] = utmCoordinates.Northing;
                        }
                        catch (Exception ex)
                        {
                            string error = $"Ошибка при преобразовании координат (Долгота WGS='{noiseValue}', Широта WGS='{zeitValue}') в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}.";
                            errorMessages.Add(error);
                            LogCalculation(error);
                        }
                    }

                    // Обработка столбца "Stan" как секунд с фиксированной даты
                    if (double.TryParse(stanValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double stanSeconds))
                    {
                        if (stanSeconds < 0)
                        {
                            string warning = $"Отрицательное значение TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                            errorMessages.Add(warning);
                            LogCalculation(warning);

                            newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                            // ComputedTime - фиксированное время минус 18 секунд (оставляет статус 00:00:00.0)
                            computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                            newRow["TIME UTC"] = computedTimeStr;
                        }
                        else
                        {
                            try
                            {
                                // Добавляем секунды к фиксированной дате
                                DateTime computedDate = fixedDate.AddSeconds(stanSeconds);

                                // Проверка, что дата находится в допустимом диапазоне
                                if (computedDate > DateTime.MaxValue || computedDate < DateTime.MinValue)
                                {
                                    string warning = $"TIME слишком велико='{stanSeconds}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                                    errorMessages.Add(warning);
                                    LogCalculation(warning);

                                    newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                    computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                                    newRow["TIME UTC"] = computedTimeStr;
                                }
                                else
                                {
                                    newRow["DATA"] = computedDate.ToString("dd.MM.yyyy");

                                    // Формат даты
                                    computedDateStr = computedDate.ToString("dd.MM.yyyy");

                                    // Вычисляем ComputedTime как остаток времени после вычитания 18 секунд
                                    TimeSpan timeOfDay = computedDate.TimeOfDay;

                                    // Вычитаем 18 секунд
                                    TimeSpan offsetTime = TimeSpan.FromSeconds(18);
                                    TimeSpan computedTime;

                                    if (timeOfDay >= offsetTime)
                                    {
                                        computedTime = timeOfDay - offsetTime;
                                    }
                                    else
                                    {
                                        // Если время дня меньше 18 секунд, устанавливаем время как 00:00:00.0
                                        computedTime = TimeSpan.Zero;
                                        string info = $"Время дня меньше 18 секунд в строке: {string.Join("\t", row)}. ComputedTime установлено как 00:00:00.0.";
                                        errorMessages.Add(info);
                                        LogCalculation(info);
                                    }

                                    // Округляем каждый TimeSpan.TotalSeconds до одного знака после запятой
                                    double totalSeconds = computedTime.TotalSeconds;
                                    double roundedSeconds = Math.Round(totalSeconds, 1, MidpointRounding.AwayFromZero);

                                    // Создаём TimeSpan из округлённого количества секунд
                                    TimeSpan roundedTime = TimeSpan.FromSeconds(roundedSeconds);

                                    // Форматируем ComputedTime с учетом одной десятичной части секунд
                                    computedTimeStr = roundedTime.ToString(@"hh\:mm\:ss\.f");
                                    newRow["TIME UTC"] = computedTimeStr;
                                }
                            }
                            catch (Exception ex)
                            {
                                string error = $"Ошибка при расчёте даты для TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}. Установка даты как фиксированной.";
                                errorMessages.Add(error);
                                LogCalculation(error);

                                newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                computedDateStr = fixedDate.ToString("dd.MM.yyyy");
                                computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                                newRow["TIME UTC"] = computedTimeStr;
                            }
                        }
                    }
                    else
                    {
                        // Если парсинг не удался, устанавливаем фиксированную дату и время, и логируем
                        string error = $"Не удалось распарсить TIME='{stanValue}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                        errorMessages.Add(error);
                        LogCalculation(error);

                        newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                        computedDateStr = fixedDate.ToString("dd.MM.yyyy");
                        computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                        newRow["TIME UTC"] = computedTimeStr;
                    }

                    combinedMagneteDataTable.Rows.Add(newRow);
                    sequentialNumber++;

                    // Логируем расчёты для каждой строки
                    string logMessage = $"TIME: '{stanValue}', ComputedDate: {computedDateStr}, ComputedTime: {computedTimeStr}";
                    LogCalculation(logMessage);
                }
            }

            // Создаём DataGrid с явно определёнными столбцами
            DataGrid dataGridCombined = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                CanUserAddRows = false,
                CanUserDeleteRows = false,
                Margin = new Thickness(0, 0, 0, 0),
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определяем столбцы вручную с необходимым форматированием
            // 1. DATA
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "DATA",
                Binding = new Binding("DATA"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 2. DATA UTC
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "DATA UTC",
                Binding = new Binding("DATA UTC"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 3. TIME
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "TIME",
                Binding = new Binding("TIME"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // 4. TIME UTC
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "TIME UTC",
                Binding = new Binding("TIME UTC"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 5. Долгота WGS
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Долгота WGS",
                Binding = new Binding("Долгота WGS"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // 6. Широта WGS
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Широта WGS",
                Binding = new Binding("Широта WGS"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // 7. Долгота UTM
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Долгота UTM",
                Binding = new Binding("Долгота UTM") { StringFormat = "F2" },
                Width = new DataGridLength(140, DataGridLengthUnitType.Auto)
            });

            // 8. Широта UTM
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Широта UTM",
                Binding = new Binding("Широта UTM") { StringFormat = "F2" },
                Width = new DataGridLength(140, DataGridLengthUnitType.Auto)
            });

            // 9. №1
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "№1",
                Binding = new Binding("№1"),
                Width = new DataGridLength(60, DataGridLengthUnitType.Auto)
            });

            // 10. Альтитуда
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Альтитуда",
                Binding = new Binding("Альтитуда"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // 11. №2
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "№2",
                Binding = new Binding("№2"),
                Width = new DataGridLength(60, DataGridLengthUnitType.Auto)
            });

            // 12. Shark
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Shark",
                Binding = new Binding("Shark"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // Присваиваем DataTable как источник данных
            dataGridCombined.ItemsSource = combinedMagneteDataTable.DefaultView;

            // Добавляем вкладку с объединёнными данными
            TabItem tabItemCombined = new TabItem
            {
                Header = ".magnete данные объединённые",
                Content = dataGridCombined
            };

            MagneteFilesTabControl.Items.Add(tabItemCombined);

            // Активируем кнопку редактирования и экспорта
            EditExportButton.IsEnabled = true;

            // Логируем основные события
            LogCalculation($"Файлы .magnete успешно объединены. Объединено строк: {combinedMagneteDataTable.Rows.Count}.");

            if (errorMessages.Any())
            {
                string allErrors = string.Join("\n", errorMessages);
                LogCalculation($"Обнаружены следующие проблемы:\n{allErrors}");
                MessageBox.Show($"Файлы .magnete объединены с ошибками. Подробности в файле логов.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                MessageBox.Show($"Файлы .magnete успешно объединены. Объединено строк: {combinedMagneteDataTable.Rows.Count}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        // Метод для добавления объединённой вкладки
        private void AddCombinedDataTab(DataTable combinedData, string header, TabControl parentTabControl)
        {
            // Проверяем, существует ли уже вкладка для объединённых данных
            foreach (TabItem tab in parentTabControl.Items)
            {
                if (tab.Header.ToString() == header)
                    return; // Вкладка уже существует
            }

            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов
            dataGrid.Columns.Clear();
            foreach (DataColumn column in combinedData.Columns)
            {
                dataGrid.Columns.Add(new DataGridTextColumn
                {
                    Header = column.ColumnName,
                    Binding = new Binding(column.ColumnName),
                    Width = new DataGridLength(1, DataGridLengthUnitType.Star)
                });
            }

            dataGrid.ItemsSource = combinedData.DefaultView;

            TabItem combinedTab = new TabItem
            {
                Header = header,
                Content = dataGrid
            };

            parentTabControl.Items.Add(combinedTab);
        }

        // Метод для преобразования объединённых данных
        private DataTable ConvertToCombinedDataTable(List<string[]> data, bool isTxt)
        {
            DataTable dt = new DataTable();

            if (isTxt)
            {
                dt.Columns.Add("№", typeof(int));
                dt.Columns.Add("BASE-1", typeof(string));
                dt.Columns.Add("Noise", typeof(string));
                dt.Columns.Add("Time", typeof(string));
                dt.Columns.Add("Time UTC", typeof(string));
            }
            else
            {
                // Для объединения .magnete файлов
                // Столбцы уже добавлены при инициализации
            }

            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                if (isTxt)
                {
                    dr["№"] = int.Parse(row[0]);
                    dr["BASE-1"] = row[1];
                    dr["Noise"] = row[2];
                    dr["Time"] = row[3];
                    dr["Time UTC"] = row[4];
                }
                else
                {
                    // Для .magnete файлов ничего не требуется здесь
                }
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчик редактирования и экспорта

        // Обработчик кнопки Редактировать и Экспортировать
        private void EditAndExport_Click(object sender, RoutedEventArgs e)
        {
            if (combinedTxtDataTable == null && combinedMagneteDataTable == null)
            {
                MessageBox.Show("Нет объединённых данных для редактирования.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Создаём и открываем окно выбора типа данных для редактирования
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите данные для редактирования:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && combinedTxtDataTable != null)
                {
                    OpenEditWindow(combinedTxtDataTable, "Редактирование .txt данных");
                }
                else if (selectedOption == ".magnete файлы" && combinedMagneteDataTable != null)
                {
                    OpenEditWindow(combinedMagneteDataTable, "Редактирование .magnete данных");
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или данные отсутствуют.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Метод для открытия окна редактирования
        private void OpenEditWindow(DataTable dataTable, string windowTitle)
        {
            EditWindow editWindow = new EditWindow(dataTable)
            {
                Title = windowTitle,
                Owner = this
            };
            editWindow.ShowDialog();

            // После редактирования данные могут быть обновлены. При необходимости обновите отображение.
            // Например, обновляем вкладки с объединёнными данными
            // Здесь предполагается, что EditWindow модифицирует переданный DataTable напрямую
        }

        #endregion

        #region Обработчик кнопки "Проект"

        // Обработчик кнопки "Проект"
        private void ProjectButton_Click(object sender, RoutedEventArgs e)
        {
            Button button = sender as Button;
            if (button != null && button.ContextMenu != null)
            {
                button.ContextMenu.PlacementTarget = button;
                button.ContextMenu.Placement = PlacementMode.Bottom;
                button.ContextMenu.IsOpen = true;
            }
        }

        #endregion

        #region Сохранение и загрузка проекта

        // Обработчик кнопки "Сохранить проект"
        private void SaveProject_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn",
                FileName = "project.magn"
            };

            if (saveFileDialog.ShowDialog() == true)
            {
                ProjectData project = new ProjectData
                {
                    TxtFileData = this.txtFileData,
                    MagneteFileData = this.magneteFileData,
                    CombinedTxtDataTable = this.combinedTxtDataTable,
                    CombinedMagneteDataTable = this.combinedMagneteDataTable
                };

                try
                {
                    // Сериализуем объект в JSON
                    string json = JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings
                    {
                        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                    });
                    File.WriteAllText(saveFileDialog.FileName, json, System.Text.Encoding.UTF8);
                    StatusTextBlock.Text = $"Проект сохранён: {saveFileDialog.FileName}";
                    MessageBox.Show("Проект успешно сохранён.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Обработчик кнопки "Загрузить проект"
        private void LoadProject_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    string json = File.ReadAllText(openFileDialog.FileName, System.Text.Encoding.UTF8);
                    ProjectData project = JsonConvert.DeserializeObject<ProjectData>(json);

                    if (project != null)
                    {
                        // Загрузка данных .txt файлов
                        this.txtFileData = project.TxtFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateTxtTabControl();

                        // Загрузка данных .magnete файлов
                        this.magneteFileData = project.MagneteFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateMagneteTabControl();

                        // Загрузка объединённых DataTables
                        this.combinedTxtDataTable = project.CombinedTxtDataTable;
                        this.combinedMagneteDataTable = project.CombinedMagneteDataTable;

                        // Деактивируем кнопку редактирования до объединения
                        EditExportButton.IsEnabled = false;

                        // Обновляем вкладки с объединёнными данными, если они существуют
                        if (combinedTxtDataTable != null)
                        {
                            AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", TxtFilesTabControl);
                        }

                        if (combinedMagneteDataTable != null)
                        {
                            AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
                        }

                        StatusTextBlock.Text = $"Проект загружен: {openFileDialog.FileName}";
                        MessageBox.Show("Проект успешно загружен.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        MessageBox.Show("Не удалось загрузить данные проекта.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при загрузке проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        #endregion

        #region Методы для объединения .magnete файлов (оригинальная логика)

        // Метод логирования расчётов и ошибок
        private void LogCalculation(string message, string logFileName = "calculation_log.txt")
        {
            string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
            try
            {
                File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для определения UTM-зоны по долготе
        private int GetUTMZone(double longitude)
        {
            // Убедитесь, что долгота находится в диапазоне от -180 до 180 градусов
            if (longitude < -180 || longitude > 180)
                throw new ArgumentOutOfRangeException("Долгота должна быть в диапазоне от -180 до 180 градусов.");

            // Формула для расчёта зоны
            int zone = (int)Math.Floor((longitude + 180) / 6) + 1;
            return zone;
        }

        // Метод для преобразования WGS84 координат в UTM
        private (double Easting, double Northing) ConvertWGS84ToUTM(double longitude, double latitude, int utmZone)
        {
            // Определяем систему координат WGS84
            var wgs84 = GeographicCoordinateSystem.WGS84;

            // Определяем систему координат UTM для данного пояса и полушария
            var utmCS = ProjectedCoordinateSystem.WGS84_UTM(utmZone, latitude >= 0);

            // Создаём трансформацию
            var transformFactory = new CoordinateTransformationFactory();
            var transformation = transformFactory.CreateFromCoordinateSystems(wgs84, utmCS);

            // Преобразование координат
            double[] fromPoint = new double[] { longitude, latitude };
            double[] toPoint = transformation.MathTransform.Transform(fromPoint);

            return (toPoint[0], toPoint[1]); // Easting, Northing
        }

        #endregion

        #region Обработчик кнопки "Графики"

        // Обработчик кнопки "Графики"
        private void ShowGraphs_Click(object sender, RoutedEventArgs e)
        {
            if (combinedTxtDataTable == null && combinedMagneteDataTable == null)
            {
                MessageBox.Show("Нет объединённых данных для построения графиков.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            GraphWindow graphWindow = new GraphWindow(combinedTxtDataTable, combinedMagneteDataTable);
            graphWindow.Owner = this;
            graphWindow.Show();
        }

        #endregion
    }
}

<!-- GraphWindow.xaml -->
<Window x:Class="TxtProcessorApp.GraphWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="http://oxyplot.org/wpf"
        Title="Графики" Height="800" Width="1200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- График 1: .txt Time UTC vs BASE-1 -->
        <GroupBox Header=".txt Time UTC vs BASE-1" Grid.Row="0" Grid.Column="0" Margin="10">
            <oxy:PlotView x:Name="TxtBase1Plot" Model="{Binding TxtBase1Model}" />
        </GroupBox>

        <!-- График 2: .txt Time UTC vs Noise -->
        <GroupBox Header=".txt Time UTC vs Noise" Grid.Row="0" Grid.Column="1" Margin="10">
            <oxy:PlotView x:Name="TxtNoisePlot" Model="{Binding TxtNoiseModel}" />
        </GroupBox>

        <!-- График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда) -->
        <GroupBox Header=".magnete Долгота UTM vs Широта UTM (Альтитуда)" Grid.Row="1" Grid.Column="0" Margin="10">
            <oxy:PlotView x:Name="MagneteAltitudePlot" Model="{Binding MagneteAltitudeModel}" />
        </GroupBox>

        <!-- График 4: .magnete Долгота UTM vs Широта UTM (Shark) -->
        <GroupBox Header=".magnete Долгота UTM vs Широта UTM (Shark)" Grid.Row="1" Grid.Column="1" Margin="10">
            <oxy:PlotView x:Name="MagneteSharkPlot" Model="{Binding MagneteSharkModel}" />
        </GroupBox>
    </Grid>
</Window>

// GraphWindow.xaml.cs
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Data;
using System.Linq;
using System.Windows;

namespace TxtProcessorApp
{
    public partial class GraphWindow : Window
    {
        public PlotModel TxtBase1Model { get; private set; }
        public PlotModel TxtNoiseModel { get; private set; }
        public PlotModel MagneteAltitudeModel { get; private set; }
        public PlotModel MagneteSharkModel { get; private set; }

        public GraphWindow(DataTable txtData, DataTable magneteData)
        {
            InitializeComponent();
            InitializePlotModels(txtData, magneteData);
            this.DataContext = this;
        }

        private void InitializePlotModels(DataTable txtData, DataTable magneteData)
        {
            // График 1: .txt Time UTC vs BASE-1
            TxtBase1Model = new PlotModel { Title = ".txt Time UTC vs BASE-1" };
            TxtBase1Model.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtBase1Model.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "BASE-1"
            });
            var txtBase1Series = new LineSeries { Title = "BASE-1" };
            foreach (DataRow row in txtData.Rows)
            {
                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["BASE-1"].ToString(), out double base1))
                {
                    txtBase1Series.Points.Add(DateTimeAxis.CreateDataPoint(timeUtc, base1));
                }
            }
            TxtBase1Model.Series.Add(txtBase1Series);

            // График 2: .txt Time UTC vs Noise
            TxtNoiseModel = new PlotModel { Title = ".txt Time UTC vs Noise" };
            TxtNoiseModel.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtNoiseModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Noise"
            });
            var txtNoiseSeries = new LineSeries { Title = "Noise" };
            foreach (DataRow row in txtData.Rows)
            {
                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["Noise"].ToString(), out double noise))
                {
                    txtNoiseSeries.Points.Add(DateTimeAxis.CreateDataPoint(timeUtc, noise));
                }
            }
            TxtNoiseModel.Series.Add(txtNoiseSeries);

            // График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
            MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
            MagneteAltitudeModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            });
            MagneteAltitudeModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            });
            var magneteAltitudeSeries = new ScatterSeries { Title = "Альтитуда", MarkerType = MarkerType.Circle };
            foreach (DataRow row in magneteData.Rows)
            {
                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Альтитуда"].ToString(), out double altitude))
                {
                    magneteAltitudeSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, altitude));
                }
            }
            MagneteAltitudeModel.Axes.Add(new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Альтитуда"
            });
            MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);

            // График 4: .magnete Долгота UTM vs Широта UTM (Shark)
            MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
            MagneteSharkModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            });
            MagneteSharkModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            });
            var magneteSharkSeries = new ScatterSeries { Title = "Shark", MarkerType = MarkerType.Circle };
            foreach (DataRow row in magneteData.Rows)
            {
                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Shark"].ToString(), out double shark))
                {
                    magneteSharkSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, shark));
                }
            }
            MagneteSharkModel.Axes.Add(new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Shark"
            });
            MagneteSharkModel.Series.Add(magneteSharkSeries);
        }
    }
}

<!-- SelectEditTypeWindow.xaml -->
<Window x:Class="TxtProcessorApp.SelectEditTypeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Выбор данных" Height="200" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Text="Выберите данные:" Grid.Row="0" Margin="0,0,0,10" FontWeight="Bold" />

        <ListBox x:Name="OptionsListBox" Grid.Row="1">
            <ListBoxItem Content=".txt файлы" />
            <ListBoxItem Content=".magnete файлы" />
        </ListBox>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

// SelectEditTypeWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;

namespace TxtProcessorApp
{
    public partial class SelectEditTypeWindow : Window
    {
        public string SelectedOption { get; private set; }

        public SelectEditTypeWindow(string prompt)
        {
            InitializeComponent();
            // Можно добавить дополнительную настройку окна при необходимости
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            if (OptionsListBox.SelectedItem is ListBoxItem selectedItem)
            {
                SelectedOption = selectedItem.Content.ToString();
                DialogResult = true;
            }
            else
            {
                MessageBox.Show("Пожалуйста, выберите опцию.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }
    }
}

<!-- EditWindow.xaml -->
<Window x:Class="TxtProcessorApp.EditWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TxtProcessorApp"
        Title="Редактирование данных" Height="600" Width="900">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- DataGrid для отображения и редактирования данных -->
        <DataGrid x:Name="EditDataGrid" Grid.Row="0" AutoGenerateColumns="True" 
                  CanUserAddRows="True" CanUserDeleteRows="True" 
                  IsReadOnly="False" 
                  SelectionUnit="CellOrRowHeader" 
                  SelectedCellsChanged="EditDataGrid_SelectedCellsChanged"
                  FontFamily="Consolas" FontSize="12">
            <!-- Контекстное меню для вставки строк и столбцов -->
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Вставить строку выше" Click="InsertRowAbove_Click"/>
                    <MenuItem Header="Вставить строку ниже" Click="InsertRowBelow_Click"/>
                    <Separator/>
                    <MenuItem Header="Вставить столбец слева" Click="InsertColumnLeft_Click"/>
                    <MenuItem Header="Вставить столбец справа" Click="InsertColumnRight_Click"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>

        <!-- Панель кнопок ниже DataGrid -->
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
            <!-- Элемент управления для выбора разделителя -->
            <ComboBox x:Name="DelimiterComboBox" Width="100" Height="30" SelectedIndex="0">
                <ComboBoxItem Content="Пробел"/>
                <ComboBoxItem Content="Табуляция"/>
                <ComboBoxItem Content="Запятая"/>
            </ComboBox>
            <Button Content="Экспортировать файл" Width="150" Height="30" Margin="10,0,0,0" Click="ExportButton_Click"/>
        </StackPanel>
    </Grid>
</Window>

// EditWindow.xaml.cs
using Microsoft.Win32;
using System;
using System.Data;
using System.Linq;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using ProjNet.CoordinateSystems.Transformations;
using ProjNet.CoordinateSystems;

namespace TxtProcessorApp
{
    /// <summary>
    /// Логика взаимодействия для EditWindow.xaml
    /// </summary>
    public partial class EditWindow : Window
    {
        private DataTable _dataTable;

        // Словарь для сопоставления исходных заголовков с заголовками для экспорта
        private readonly Dictionary<string, string> exportHeaderMapping = new Dictionary<string, string>
        {
            { "No1", "№1" },
            { "No2", "№2" }
            // Добавьте другие сопоставления, если необходимо
        };

        public EditWindow(DataTable dataTable)
        {
            InitializeComponent();
            _dataTable = dataTable;
            EditDataGrid.ItemsSource = _dataTable.DefaultView;

            // Разрешаем добавление и удаление строк через интерфейс DataGrid
            EditDataGrid.CanUserAddRows = true;
            EditDataGrid.CanUserDeleteRows = true;
        }

        #region Вставка строк и столбцов

        // Вставка строки выше выбранной
        private void InsertRowAbove_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                _dataTable.Rows.InsertAt(_dataTable.NewRow(), rowIndex);
            }
            else
            {
                // Если ничего не выбрано, вставляем в конец
                _dataTable.Rows.Add(_dataTable.NewRow());
            }

            RefreshDataGrid();
        }

        // Вставка строки ниже выбранной
        private void InsertRowBelow_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                _dataTable.Rows.InsertAt(_dataTable.NewRow(), rowIndex + 1);
            }
            else
            {
                // Если ничего не выбрано, вставляем в конец
                _dataTable.Rows.Add(_dataTable.NewRow());
            }

            RefreshDataGrid();
        }

        // Вставка столбца слева от выбранного
        private void InsertColumnLeft_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex, "NewColumn");
            RefreshDataGrid();
        }

        // Вставка столбца справа от выбранного
        private void InsertColumnRight_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex + 1, "NewColumn");
            RefreshDataGrid();
        }

        // Добавление нового столбца в указанное место
        private void InsertNewColumn(int index, string defaultName)
        {
            // Предлагаем пользователю ввести имя нового столбца
            InputDialog inputDialog = new InputDialog("Введите имя нового столбца:", defaultName);
            if (inputDialog.ShowDialog() == true)
            {
                string columnName = inputDialog.ResponseText.Trim();
                if (string.IsNullOrEmpty(columnName))
                {
                    MessageBox.Show("Имя столбца не может быть пустым.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                // Проверяем, нет ли уже столбца с таким именем
                if (_dataTable.Columns.Contains(columnName))
                {
                    MessageBox.Show("Столбец с таким именем уже существует.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                // Создаём новый столбец
                DataColumn newColumn = new DataColumn(columnName, typeof(string));

                // Вставляем столбец в нужное место
                _dataTable.Columns.Add(newColumn);
                newColumn.SetOrdinal(Math.Min(index, _dataTable.Columns.Count - 1));

                // Устанавливаем значение по умолчанию для существующих строк
                foreach (DataRow row in _dataTable.Rows)
                {
                    row[newColumn] = string.Empty;
                }
            }
            else
            {
                // Если пользователь отменил ввод, автоматически генерируем уникальное имя
                string autoColumnName = GenerateUniqueColumnName("NewColumn");
                DataColumn newColumn = new DataColumn(autoColumnName, typeof(string));
                _dataTable.Columns.Add(newColumn);
                newColumn.SetOrdinal(Math.Min(index, _dataTable.Columns.Count - 1));

                foreach (DataRow row in _dataTable.Rows)
                {
                    row[newColumn] = string.Empty;
                }
            }
        }

        // Генерация уникального имени столбца
        private string GenerateUniqueColumnName(string baseName)
        {
            int suffix = 1;
            string newName = baseName;
            while (_dataTable.Columns.Contains(newName))
            {
                newName = $"{baseName}_{suffix}";
                suffix++;
            }
            return newName;
        }

        // Получение индекса выбранной строки
        private int GetSelectedRowIndex()
        {
            if (EditDataGrid.SelectedItem is DataRowView selectedRow)
            {
                return _dataTable.Rows.IndexOf(selectedRow.Row);
            }
            return -1;
        }

        // Получение индекса выбранного столбца
        private int GetSelectedColumnIndex()
        {
            if (EditDataGrid.SelectedCells.Count > 0)
            {
                var selectedCell = EditDataGrid.SelectedCells[0];
                var column = selectedCell.Column;
                if (column != null)
                {
                    return column.DisplayIndex;
                }
            }
            return -1;
        }

        #endregion

        // Метод для обновления привязки данных к DataGrid
        private void RefreshDataGrid()
        {
            // Получаем текущий view
            var view = CollectionViewSource.GetDefaultView(EditDataGrid.ItemsSource);

            if (view != null)
            {
                view.Refresh();
            }
        }

        // Обработчик изменения выбранных ячеек (опционально)
        private void EditDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            // Дополнительная логика при изменении выбора (если требуется)
        }

        // Обработчик кнопки Экспортировать файл
        private void ExportButton_Click(object sender, RoutedEventArgs e)
        {
            // Выбор разделителя
            string delimiter = GetSelectedDelimiter();

            SaveFileDialog sfd = new SaveFileDialog
            {
                Filter = delimiter == "," ? "CSV файлы (*.csv)|*.csv" : "Текстовые файлы (*.txt)|*.txt",
                FileName = delimiter == "," ? "EditedCombinedFile.csv" : "EditedCombinedFile.txt"
            };

            if (sfd.ShowDialog() == true)
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(sfd.FileName, false, Encoding.UTF8))
                    {
                        if (delimiter == " ")
                        {
                            // Фиксированная ширина с использованием PadRight
                            ExportWithFixedWidth(sw, delimiter);
                        }
                        else
                        {
                            // Экспорт в CSV или табуляцию
                            ExportWithDelimiter(sw, delimiter);
                        }
                    }

                    MessageBox.Show($"Файл успешно экспортирован по адресу:\n{sfd.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении файла:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Экспорт данных с фиксированной шириной столбцов, используя PadRight.
        /// Подходит для текстовых файлов с пробелом в качестве разделителя.
        /// </summary>
        /// <param name="sw">Поток для записи.</param>
        /// <param name="delimiter">Разделитель.</param>
        private void ExportWithFixedWidth(StreamWriter sw, string delimiter)
        {
            var columns = _dataTable.Columns.Cast<DataColumn>().ToList();

            // Шаг 1: Вычисляем максимальную ширину для каждого столбца
            int[] maxColWidths = new int[columns.Count];

            for (int i = 0; i < columns.Count; i++)
            {
                int maxWidth = columns[i].ColumnName.Length;

                foreach (DataRow row in _dataTable.Rows)
                {
                    string field = row[i]?.ToString() ?? string.Empty;
                    if (field.Length > maxWidth)
                    {
                        maxWidth = field.Length;
                    }
                }

                maxColWidths[i] = maxWidth;
            }

            // Шаг 2: Запись заголовков с padding
            string headerLine = string.Empty;
            for (int i = 0; i < columns.Count; i++)
            {
                string header = columns[i].ColumnName;
                headerLine += header.PadRight(maxColWidths[i] + 2); // Добавляем 2 пробела между колонками
            }

            sw.WriteLine(headerLine);

            // Шаг 3: Запись данных строк с padding
            foreach (DataRow row in _dataTable.Rows)
            {
                string dataLine = string.Empty;
                for (int i = 0; i < columns.Count; i++)
                {
                    string field = row[i]?.ToString() ?? string.Empty;
                    dataLine += field.PadRight(maxColWidths[i] + 2);
                }
                sw.WriteLine(dataLine);
            }
        }

        /// <summary>
        /// Экспорт данных с использованием указанного разделителя (CSV или табуляция).
        /// </summary>
        /// <param name="sw">Поток для записи.</param>
        /// <param name="delimiter">Разделитель.</param>
        private void ExportWithDelimiter(StreamWriter sw, string delimiter)
        {
            // Запись заголовков
            string[] headers = _dataTable.Columns
                .Cast<DataColumn>()
                .Select(column =>
                {
                    if (exportHeaderMapping.ContainsKey(column.ColumnName))
                        return $"\"{exportHeaderMapping[column.ColumnName]}\"";
                    else
                        return $"\"{column.ColumnName}\"";
                })
                .ToArray();
            sw.WriteLine(string.Join(delimiter, headers));

            // Запись данных строк
            foreach (DataRow row in _dataTable.Rows)
            {
                string[] fields = row.ItemArray.Select(field =>
                {
                    string fieldStr = field?.ToString() ?? string.Empty;
                    // Если поле содержит разделитель или кавычки, обрамляем его в кавычки и экранируем двойные кавычки
                    if (fieldStr.Contains(delimiter) || fieldStr.Contains("\""))
                    {
                        fieldStr = $"\"{fieldStr.Replace("\"", "\"\"")}\"";
                    }
                    return fieldStr;
                }).ToArray();
                sw.WriteLine(string.Join(delimiter, fields));
            }
        }

        /// <summary>
        /// Метод для получения выбранного разделителя.
        /// </summary>
        /// <returns>Разделитель в виде строки.</returns>
        private string GetSelectedDelimiter()
        {
            switch (DelimiterComboBox.SelectedIndex)
            {
                case 0:
                    return " "; // Пробел
                case 1:
                    return "\t"; // Табуляция
                case 2:
                    return ","; // Запятая
                default:
                    return "\t"; // По умолчанию табуляция
            }
        }



        #region Методы для объединения .magnete файлов (оригинальная логика)

        // Метод логирования расчётов и ошибок
        private void LogCalculation(string message, string logFileName = "calculation_log.txt")
        {
            string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
            try
            {
                File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для определения UTM-зоны по долготе
        private int GetUTMZone(double longitude)
        {
            // Убедитесь, что долгота находится в диапазоне от -180 до 180 градусов
            if (longitude < -180 || longitude > 180)
                throw new ArgumentOutOfRangeException("Долгота должна быть в диапазоне от -180 до 180 градусов.");

            // Формула для расчёта зоны
            int zone = (int)Math.Floor((longitude + 180) / 6) + 1;
            return zone;
        }

        // Метод для преобразования WGS84 координат в UTM
        private (double Easting, double Northing) ConvertWGS84ToUTM(double longitude, double latitude, int utmZone)
        {
            // Определяем систему координат WGS84
            var wgs84 = GeographicCoordinateSystem.WGS84;

            // Определяем систему координат UTM для данного пояса и полушария
            var utmCS = ProjectedCoordinateSystem.WGS84_UTM(utmZone, latitude >= 0);

            // Создаём трансформацию
            var transformFactory = new CoordinateTransformationFactory();
            var transformation = transformFactory.CreateFromCoordinateSystems(wgs84, utmCS);

            // Преобразование координат
            double[] fromPoint = new double[] { longitude, latitude };
            double[] toPoint = transformation.MathTransform.Transform(fromPoint);

            return (toPoint[0], toPoint[1]); // Easting, Northing
        }

        #endregion
    }
}

<Window x:Class="TxtProcessorApp.InputDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ввод" Height="150" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock x:Name="PromptTextBlock" Grid.Row="0" Text="Prompt:" Margin="0,0,0,10" TextWrapping="Wrap"/>

        <TextBox x:Name="ResponseTextBox" Grid.Row="1" Height="25"/>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

using System.Windows;

namespace TxtProcessorApp
{
    /// <summary>
    /// Логика взаимодействия для InputDialog.xaml
    /// </summary>
    public partial class InputDialog : Window
    {
        public string ResponseText { get; private set; }

        public InputDialog(string question, string defaultAnswer = "")
        {
            InitializeComponent();
            PromptTextBlock.Text = question;
            ResponseTextBox.Text = defaultAnswer;
            ResponseTextBox.SelectAll();
            ResponseTextBox.Focus();
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            ResponseText = ResponseTextBox.Text;
            DialogResult = true;
        }
    }
}

// ProjectData.cs
using System.Collections.Generic;
using System.Data;

namespace TxtProcessorApp
{
    public class ProjectData
    {
        public Dictionary<string, List<string[]>> TxtFileData { get; set; }
        public Dictionary<string, List<string[]>> MagneteFileData { get; set; }
        public DataTable CombinedTxtDataTable { get; set; }
        public DataTable CombinedMagneteDataTable { get; set; }
    }
}