USER
в окне graphwindow можно удалять точки с графиков
эти графики строятся из файлов которые получаются после нажатия кнопки сформировать файл в mainwindow
я хочу чтобы после удаления точек можно было сохранить изменения и отобразить новый файл в окнах в mainwindow где отображаются объединенные файлы после нажатия сформировать
пиши полные коды которые были изменены
// 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; }
}
}
<!-- Строка состояния -->
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="StatusTextBlock" Text="Готово"/>
</StatusBarItem>
</StatusBar>
<!-- Основная область с данными -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Левая область для файлов -->
<GroupBox Header=".txt Файлы" Margin="0,10,5,10">
<TabControl x:Name="FilesTabControl"/>
</GroupBox>
<!-- Правая область для файлов -->
<GroupBox Header=".magnete Файлы" Grid.Column="2" Margin="5,10,0,10">
<TabControl x:Name="MagneteFilesTabControl"/>
</GroupBox>
<!-- Разделитель -->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="Gray"/>
</Grid>
</DockPanel>
// MainWindow.xaml.cs
using Microsoft.Win32;
using Newtonsoft.Json;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
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 Обработчики загрузки файлов
// Обработчик кнопок загрузки файлов
private async void LoadFiles_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is string fileType)
{
string filter = fileType == "txt" ? "Текстовые файлы (*.txt)|*.txt" : "Magnete файлы (*.magnete)|*.magnete";
OpenFileDialog ofd = new OpenFileDialog
{
Multiselect = true,
Filter = filter
};
if (ofd.ShowDialog() == true)
{
foreach (var file in ofd.FileNames)
{
await ProcessFileAsync(file, fileType);
}
// Обновляем соответствующий TabControl
if (fileType == "txt")
{
PopulateTabControl(FilesTabControl, txtFileData, CreateTxtDataGrid);
combinedTxtDataTable = null;
}
else
{
PopulateTabControl(MagneteFilesTabControl, magneteFileData, CreateMagneteDataGrid);
combinedMagneteDataTable = null;
}
EditExportButton.IsEnabled = false;
StatusTextBlock.Text = $"Загружено {(fileType == "txt" ? txtFileData.Count : magneteFileData.Count)} {(fileType == "txt" ? ".txt" : ".magnete")} файлов.";
}
}
}
// Универсальный метод для обработки файлов
private Task ProcessFileAsync(string filePath, string fileType)
{
return Task.Run(() =>
{
List<string[]> processedLines = new List<string[]>();
try
{
var lines = File.ReadAllLines(filePath);
if (fileType == "txt")
{
lines = lines.Skip(11).ToArray(); // Пропуск первых 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;
// Добавляем столбцы в зависимости от типа файла
if (fileType == "txt")
{
// Для .txt пропускаем первый столбец
processedLines.Add(columns.Skip(1).ToArray());
}
else
{
// Для .magnete добавляем все столбцы
processedLines.Add(columns);
}
}
// Добавляем обработанные данные в словарь
string fileName = Path.GetFileName(filePath);
string uniqueFileName = GetUniqueFileName(fileName, fileType == "txt" ? txtFileData : magneteFileData);
if (fileType == "txt")
{
lock (txtFileData)
{
txtFileData[uniqueFileName] = processedLines;
}
}
else
{
lock (magneteFileData)
{
magneteFileData[uniqueFileName] = processedLines;
}
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error));
}
});
}
// Метод для генерации уникального имени файла
private string GetUniqueFileName(string fileName, Dictionary<string, List<string[]>> existingData)
{
string originalFileName = fileName;
int duplicateCount = 1;
while (existingData.ContainsKey(fileName))
{
fileName = $"{Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{Path.GetExtension(originalFileName)}";
duplicateCount++;
}
return fileName;
}
// Метод для заполнения TabControl
private void PopulateTabControl(TabControl tabControl, Dictionary<string, List<string[]>> fileData, Func<List<string[]>, DataGrid> createGrid)
{
Dispatcher.Invoke(() =>
{
tabControl.Items.Clear();
foreach (var kvp in fileData)
{
TabItem tabItem = new TabItem
{
Header = kvp.Key,
Content = createGrid(kvp.Value)
};
tabControl.Items.Add(tabItem);
}
});
}
// Создание DataGrid для .txt файлов
private DataGrid CreateTxtDataGrid(List<string[]> data)
{
DataTable dt = ConvertToDataTable(data, isTxt: true);
return CreateDataGrid(dt, isEditable: false);
}
// Создание DataGrid для .magnete файлов
private DataGrid CreateMagneteDataGrid(List<string[]> data)
{
DataTable dt = ConvertToDataTable(data, isTxt: false);
return CreateDataGrid(dt, isEditable: false);
}
// Универсальный метод для создания DataGrid
private DataGrid CreateDataGrid(DataTable dataTable, bool isEditable)
{
DataGrid dataGrid = new DataGrid
{
AutoGenerateColumns = true,
ItemsSource = dataTable.DefaultView,
IsReadOnly = !isEditable,
FontFamily = new System.Windows.Media.FontFamily("Consolas"),
FontSize = 12,
Margin = new Thickness(5)
};
return dataGrid;
}
// Универсальный метод для преобразования данных в DataTable
private DataTable ConvertToDataTable(List<string[]> data, bool isTxt)
{
DataTable dt = new DataTable();
if (data == null || data.Count == 0)
return dt;
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, предполагая фиксированное количество столбцов
// Можно динамически определять, если требуется
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));
}
int number = 1;
foreach (var row in data)
{
DataRow dr = dt.NewRow();
if (isTxt)
{
dr["№"] = number++;
dr["BASE-1"] = 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["Time UTC"] = row.Length > 3 ? row[3] : string.Empty;
}
else
{
dr["Stan"] = row.Length > 0 ? row[0] : string.Empty;
dr["Zeit"] = row.Length > 1 ? row[1] : string.Empty;
dr["Noise"] = row.Length > 2 ? row[2] : string.Empty;
dr["Time"] = row.Length > 3 ? row[3] : string.Empty;
dr["TimeUTC"] = row.Length > 4 ? row[4] : string.Empty;
}
dt.Rows.Add(dr);
}
return dt;
}
#endregion
#region Обработчик объединения файлов
// Обработчик кнопки Объединить файлы
private async 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())
{
await CombineTxtFilesAsync();
}
else if (selectedOption == ".magnete файлы" && magneteFileData.Any())
{
await CombineMagneteFilesAsync();
}
else
{
MessageBox.Show("Выбранная опция недоступна или нет загруженных файлов данного типа.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
// Асинхронный метод для объединения .txt файлов
private Task CombineTxtFilesAsync()
{
return Task.Run(() =>
{
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;
string secondColumn = row[1];
string thirdColumn = row.Length > 1 ? row[2] : string.Empty;
string fourthColumn = row.Length > 2 ? row[3] : "0";
string timeString = "InvalidNumber";
if (double.TryParse(fourthColumn, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
{
double divided = number / 24;
TimeSpan time = TimeSpan.FromDays(divided);
timeString = time.ToString(@"hh\:mm\:ss");
}
combinedData.Add(new string[]
{
lineNumber.ToString(),
secondColumn,
thirdColumn,
fourthColumn,
timeString
});
lineNumber++;
}
}
combinedTxtDataTable = ConvertToCombinedDataTable(combinedData, isTxt: true);
Dispatcher.Invoke(() =>
{
AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", FilesTabControl);
EditExportButton.IsEnabled = true;
StatusTextBlock.Text = $"Объединены .txt файлы. Строк: {combinedTxtDataTable.Rows.Count}.";
});
});
}
// Асинхронный метод для объединения .magnete файлов
private Task CombineMagneteFilesAsync()
{
return Task.Run(() =>
{
if (!magneteFileData.Any())
{
Dispatcher.Invoke(() =>
MessageBox.Show("Нет загруженных .magnete файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information));
return;
}
DateTime fixedDate = new DateTime(1980, 1, 6, 0, 0, 0, DateTimeKind.Utc);
DataTable combinedTable = new DataTable();
// Определение столбцов
Dispatcher.Invoke(() =>
{
combinedTable.Columns.Add("DATA", typeof(string));
combinedTable.Columns.Add("DATA UTC", typeof(string));
combinedTable.Columns.Add("TIME", typeof(string));
combinedTable.Columns.Add("TIME UTC", typeof(string));
combinedTable.Columns.Add("Долгота WGS", typeof(string));
combinedTable.Columns.Add("Широта WGS", typeof(string));
combinedTable.Columns.Add("Долгота UTM", typeof(double));
combinedTable.Columns.Add("Широта UTM", typeof(double));
combinedTable.Columns.Add("№1", typeof(int));
combinedTable.Columns.Add("Альтитуда", typeof(string));
combinedTable.Columns.Add("№2", typeof(int));
combinedTable.Columns.Add("Shark", typeof(string));
});
int sequentialNumber = 1;
List<string> errorMessages = new List<string>();
foreach (var file in magneteFileData.Values)
{
foreach (var row in file)
{
DataRow newRow = combinedTable.NewRow();
newRow["DATA UTC"] = fixedDate.ToString("dd.MM.yyyy");
newRow["TIME"] = row.Length > 0 ? row[0].Trim() : string.Empty;
newRow["Широта WGS"] = row.Length > 1 ? row[1].Trim() : string.Empty;
newRow["Долгота WGS"] = row.Length > 2 ? row[2].Trim() : string.Empty;
newRow["№1"] = sequentialNumber;
newRow["№2"] = sequentialNumber;
newRow["Альтитуда"] = row.Length > 3 ? row[3].Trim() : string.Empty;
newRow["Shark"] = row.Length > 4 ? row[4].Trim() : string.Empty;
newRow["Долгота UTM"] = 0.0;
newRow["Широта UTM"] = 0.0;
string stanValue = row.Length > 0 ? row[0].Trim() : string.Empty;
string zeitValue = row.Length > 1 ? row[1].Trim() : string.Empty;
string noiseValue = row.Length > 2 ? row[2].Trim() : string.Empty;
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
{
int utmZone = GetUTMZone(longitude);
var utmCoordinates = ConvertWGS84ToUTM(longitude, latitude, utmZone);
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");
newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
}
else
{
try
{
DateTime computedDate = fixedDate.AddSeconds(stanSeconds);
newRow["DATA"] = computedDate.ToString("dd.MM.yyyy");
TimeSpan offsetTime = TimeSpan.FromSeconds(18);
TimeSpan computedTime = (computedDate.TimeOfDay >= offsetTime) ? computedDate.TimeOfDay - offsetTime : TimeSpan.Zero;
double roundedSeconds = Math.Round(computedTime.TotalSeconds, 1, MidpointRounding.AwayFromZero);
TimeSpan roundedTime = TimeSpan.FromSeconds(roundedSeconds);
newRow["TIME UTC"] = roundedTime.ToString(@"hh\:mm\:ss\.f");
}
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");
newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
}
}
}
else
{
string error = $"Не удалось распарсить TIME='{stanValue}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
errorMessages.Add(error);
LogCalculation(error);
newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
}
combinedTable.Rows.Add(newRow);
sequentialNumber++;
// Логируем расчёты для каждой строки
string logMessage = $"TIME: '{stanValue}', ComputedDate: '{newRow["DATA"]}', ComputedTime: '{newRow["TIME UTC"]}'";
LogCalculation(logMessage);
}
}
Dispatcher.Invoke(() =>
{
combinedMagneteDataTable = combinedTable;
AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
EditExportButton.IsEnabled = true;
StatusTextBlock.Text = $"Объединены .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 = CreateDataGrid(combinedData, isEditable: false);
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));
}
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];
}
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();
// После редактирования данные могут быть обновлены. Обновляем соответствующие вкладки.
// Например, перегенерируем DataGrid для объединённой вкладки.
// Здесь предполагается, что EditWindow модифицирует переданный DataTable напрямую.
}
#endregion
#region Обработчик кнопки "Проект"
// Обработчик кнопки "Проект" через ContextMenu
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 async 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 = await Task.Run(() => JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
await File.WriteAllTextAsync(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 async 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 = await File.ReadAllTextAsync(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[]>>();
PopulateTabControl(FilesTabControl, txtFileData, CreateTxtDataGrid);
// Загрузка данных .magnete файлов
this.magneteFileData = project.MagneteFileData ?? new Dictionary<string, List<string[]>>();
PopulateTabControl(MagneteFilesTabControl, magneteFileData, CreateMagneteDataGrid);
// Загрузка объединённых DataTables
this.combinedTxtDataTable = project.CombinedTxtDataTable;
this.combinedMagneteDataTable = project.CombinedMagneteDataTable;
// Активируем соответствующие вкладки, если объединённые данные присутствуют
if (combinedTxtDataTable != null)
{
AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", FilesTabControl);
}
if (combinedMagneteDataTable != null)
{
AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
}
EditExportButton.IsEnabled = combinedTxtDataTable != null || combinedMagneteDataTable != null;
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)
{
Dispatcher.Invoke(() =>
MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error));
}
}
// Метод для определения UTM-зоны по долготе
private int GetUTMZone(double longitude)
{
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)
{
var wgs84 = GeographicCoordinateSystem.WGS84;
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]);
}
#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)
{
Owner = this
};
graphWindow.Show();
}
#endregion
}
}
<!-- Область для отображения графика -->
<oxy:PlotView x:Name="MainPlotView" DockPanel.Dock="Bottom" Margin="10" />
</DockPanel>
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
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; }
// Ссылки на выбранную серию и точку
private ScatterSeries selectedSeries = null;
private ScatterPoint selectedPoint = null;
// Конструктор класса
public GraphWindow(DataTable txtData, DataTable magneteData)
{
InitializeComponent();
InitializePlotModels(txtData, magneteData); // Инициализация графиков
this.DataContext = this;
// Подписка на событие нажатия мыши на PlotView
MainPlotView.MouseDown += PlotView_MouseDown;
}
/// <summary>
/// Устанавливает равный масштаб для осей X и Y.
/// </summary>
private void SetEqualAxisScaling(LinearAxis xAxis, LinearAxis yAxis, IEnumerable<DataRow> dataRows, string xColumn, string yColumn)
{
try
{
// Вычисление минимальных и максимальных значений
double minX = dataRows.Min(row => Convert.ToDouble(row[xColumn]));
double maxX = dataRows.Max(row => Convert.ToDouble(row[xColumn]));
double minY = dataRows.Min(row => Convert.ToDouble(row[yColumn]));
double maxY = dataRows.Max(row => Convert.ToDouble(row[yColumn]));
double rangeX = maxX - minX;
double rangeY = maxY - minY;
// Определение максимального диапазона
double maxRange = Math.Max(rangeX, rangeY);
// Вычисление центра диапазона
double centerX = (minX + maxX) / 2;
double centerY = (minY + maxY) / 2;
// Установка диапазонов осей для равного масштаба
xAxis.Minimum = centerX - maxRange / 2;
xAxis.Maximum = centerX + maxRange / 2;
yAxis.Minimum = centerY - maxRange / 2;
yAxis.Maximum = centerY + maxRange / 2;
// Установка основных шагов
xAxis.MajorStep = maxRange / 10;
yAxis.MajorStep = maxRange / 10;
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при установке масштаба осей: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Инициализирует модели графиков.
/// </summary>
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 ScatterSeries
{
Title = "BASE-1",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nBASE-1: {4}"
};
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))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtBase1Series.Points.Add(new ScatterPoint(timeInDouble, 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 ScatterSeries
{
Title = "Noise",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nNoise: {4}"
};
foreach (DataRow row in txtData.Rows)
{
if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
double.TryParse(row["Noise"].ToString(), out double noise))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtNoiseSeries.Points.Add(new ScatterPoint(timeInDouble, noise));
}
}
TxtNoiseModel.Series.Add(txtNoiseSeries);
// График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
var xAxisAltitude = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisAltitude = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteAltitudeModel.Axes.Add(xAxisAltitude);
MagneteAltitudeModel.Axes.Add(yAxisAltitude);
var colorAxisAltitude = new LinearColorAxis
{
Position = AxisPosition.Right,
Palette = OxyPalettes.Jet(500),
Title = "Альтитуда"
};
MagneteAltitudeModel.Axes.Add(colorAxisAltitude);
var magneteAltitudeSeries = new ScatterSeries
{
Title = "Альтитуда",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nАльтитуда: {4}"
};
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.Series.Add(magneteAltitudeSeries);
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisAltitude, yAxisAltitude, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// График 4: .magnete Долгота UTM vs Широта UTM (Shark)
MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
var xAxisShark = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisShark = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteSharkModel.Axes.Add(xAxisShark);
MagneteSharkModel.Axes.Add(yAxisShark);
var colorAxisShark = new LinearColorAxis
{
Position = AxisPosition.Right,
Palette = OxyPalettes.Jet(500),
Title = "Shark"
};
MagneteSharkModel.Axes.Add(colorAxisShark);
var magneteSharkSeries = new ScatterSeries
{
Title = "Shark",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nShark: {4}"
};
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.Series.Add(magneteSharkSeries);
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisShark, yAxisShark, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// Настройка PlotView по умолчанию
MainPlotView.Model = TxtBase1Model;
}
/// <summary>
/// Обработчик выбора графика из ComboBox.
/// </summary>
private void ChartSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItem = (sender as ComboBox)?.SelectedItem as ComboBoxItem;
if (selectedItem != null)
{
switch (selectedItem.Content.ToString())
{
case ".txt Time UTC vs BASE-1":
MainPlotView.Model = TxtBase1Model;
break;
case ".txt Time UTC vs Noise":
MainPlotView.Model = TxtNoiseModel;
break;
case ".magnete Долгота UTM vs Широта UTM (Альтитуда)":
MainPlotView.Model = MagneteAltitudeModel;
break;
case ".magnete Долгота UTM vs Широта UTM (Shark)":
MainPlotView.Model = MagneteSharkModel;
break;
default:
break;
}
}
}
/// <summary>
/// Обработчик события MouseDown для PlotView.
/// </summary>
private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
{
var plotModel = MainPlotView.Model;
if (plotModel == null)
return;
// Получить позицию клика относительно PlotView
var mousePos = e.GetPosition(MainPlotView);
// Преобразование координат клика в данные координаты
double xData, yData;
try
{
xData = plotModel.DefaultXAxis.InverseTransform(mousePos.X);
yData = plotModel.DefaultYAxis.InverseTransform(mousePos.Y);
}
catch
{
// Если преобразование не удалось, выйти
return;
}
// Определить ближайшую точку и расстояние до неё (в пикселях)
double thresholdInPixels = 10; // Пороговое расстояние в пикселях
double minDistance = double.MaxValue;
ScatterSeries nearestSeries = null;
ScatterPoint nearestPoint = null;
foreach (var series in plotModel.Series)
{
if (series is ScatterSeries scatterSeries)
{
foreach (var point in scatterSeries.Points)
{
// Перевести данные координаты в экранные
double screenX = plotModel.DefaultXAxis.Transform(point.X);
double screenY = plotModel.DefaultYAxis.Transform(point.Y);
// Вычисление расстояния между кликом и точкой
double distance = Math.Sqrt(Math.Pow(mousePos.X - screenX, 2) + Math.Pow(mousePos.Y - screenY, 2));
if (distance < minDistance && distance <= thresholdInPixels)
{
minDistance = distance;
nearestSeries = scatterSeries;
nearestPoint = point;
}
}
}
}
if (nearestSeries != null && nearestPoint != null)
{
// Сохранить выбранную серию и точку
selectedSeries = nearestSeries;
selectedPoint = nearestPoint;
// Показать контекстное меню в позиции клика
// Convert mouse position relative to window
var windowPos = this.PointToScreen(mousePos);
ShowContextMenu(new System.Windows.Point(windowPos.X, windowPos.Y));
// Обработка события завершена
e.Handled = true;
}
}
/// <summary>
/// Отображает контекстное меню с опцией удаления точки.
/// </summary>
/// <param name="position">Позиция клика для отображения меню (в координатах экрана).</param>
private void ShowContextMenu(System.Windows.Point position)
{
if (selectedSeries == null || selectedPoint == null)
return;
// Создание контекстного меню
ContextMenu contextMenu = new ContextMenu();
MenuItem deleteItem = new MenuItem
{
Header = "Удалить точку"
};
deleteItem.Click += DeleteItem_Click;
contextMenu.Items.Add(deleteItem);
// Установка позиции контекстного меню
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.AbsolutePoint;
contextMenu.HorizontalOffset = position.X;
contextMenu.VerticalOffset = position.Y;
// Открытие контекстного меню
contextMenu.IsOpen = true;
}
/// <summary>
/// Обработчик события удаления точки из контекстного меню.
/// </summary>
private void DeleteItem_Click(object sender, RoutedEventArgs e)
{
if (selectedSeries != null && selectedPoint != null)
{
var result = MessageBox.Show("Вы уверены, что хотите удалить эту точку?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
selectedSeries.Points.Remove(selectedPoint);
MainPlotView.Model.InvalidatePlot(true);
}
// Сброс выбранной точки и серии
selectedSeries = null;
selectedPoint = null;
}
}
}
}
To achieve ASSISTANT
Чтобы реализовать возможность удаления точек из графиков в окне `GraphWindow`, сохранения изменений и отображения обновленных данных в `MainWindow`, необходимо внести изменения в код `GraphWindow.xaml.cs`. Основная идея заключается в связывании удаляемых точек с соответствующими строками в `DataTable`, чтобы при удалении точки также удалялась соответствующая запись из данных. Далее эти изменения будут автоматически отражены в `MainWindow`, так как `DataGrid` привязан к `DataTable`.
Ниже приведены полные измененные коды для `GraphWindow.xaml.cs` и необходимые изменения в `MainWindow.xaml.cs`, если они потребуются.
## Изменения в `GraphWindow.xaml.cs`
### 1. Добавьте поле для сопоставления точек с DataRow
Добавьте в класс `GraphWindow` следующее поле, которое будет хранить сопоставление между `ScatterSeries` и списком соответствующих `DataRow`:
```csharp
private Dictionary<ScatterSeries, List<DataRow>> seriesDataRows = new Dictionary<ScatterSeries, List<DataRow>>();
```
### 2. Измените метод `InitializePlotModels` для заполнения `seriesDataRows`
Модифицируйте метод `InitializePlotModels`, чтобы при добавлении точек в графики сохранять соответствующие им `DataRow`. Это позволит связать удаляемую точку с её строкой данных.
```csharp
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 ScatterSeries
{
Title = "BASE-1",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nBASE-1: {4}"
};
List<DataRow> txtBase1DataRows = new List<DataRow>();
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))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtBase1Series.Points.Add(new ScatterPoint(timeInDouble, base1));
txtBase1DataRows.Add(row);
}
}
TxtBase1Model.Series.Add(txtBase1Series);
seriesDataRows[txtBase1Series] = txtBase1DataRows;
// Инициализация графика 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 ScatterSeries
{
Title = "Noise",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nNoise: {4}"
};
List<DataRow> txtNoiseDataRows = new List<DataRow>();
foreach (DataRow row in txtData.Rows)
{
if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
double.TryParse(row["Noise"].ToString(), out double noise))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtNoiseSeries.Points.Add(new ScatterPoint(timeInDouble, noise));
txtNoiseDataRows.Add(row);
}
}
TxtNoiseModel.Series.Add(txtNoiseSeries);
seriesDataRows[txtNoiseSeries] = txtNoiseDataRows;
// Инициализация графика 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
var xAxisAltitude = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisAltitude = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteAltitudeModel.Axes.Add(xAxisAltitude);
MagneteAltitudeModel.Axes.Add(yAxisAltitude);
var magneteAltitudeSeries = new ScatterSeries
{
Title = "Альтитуда",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nАльтитуда: {4}"
};
List<DataRow> magneteAltitudeDataRows = new List<DataRow>();
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));
magneteAltitudeDataRows.Add(row);
}
}
MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);
seriesDataRows[magneteAltitudeSeries] = magneteAltitudeDataRows;
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisAltitude, yAxisAltitude, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// Инициализация графика 4: .magnete Долгота UTM vs Широта UTM (Shark)
MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
var xAxisShark = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisShark = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteSharkModel.Axes.Add(xAxisShark);
MagneteSharkModel.Axes.Add(yAxisShark);
var magneteSharkSeries = new ScatterSeries
{
Title = "Shark",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nShark: {4}"
};
List<DataRow> magneteSharkDataRows = new List<DataRow>();
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));
magneteSharkDataRows.Add(row);
}
}
MagneteSharkModel.Series.Add(magneteSharkSeries);
seriesDataRows[magneteSharkSeries] = magneteSharkDataRows;
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisShark, yAxisShark, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// Настройка PlotView по умолчанию
MainPlotView.Model = TxtBase1Model;
}
```
### 3. Измените обработчик удаления точки
Модифицируйте метод `DeleteItem_Click`, чтобы удалять соответствующую строку из `DataTable` при удалении точки из графика.
```csharp
private void DeleteItem_Click(object sender, RoutedEventArgs e)
{
if (selectedSeries != null && selectedPoint != null)
{
var result = MessageBox.Show("Вы уверены, что хотите удалить эту точку?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
// Найти соответствующую DataRow
var dataRows = seriesDataRows[selectedSeries];
int pointIndex = selectedSeries.Points.IndexOf(selectedPoint);
if (pointIndex >= 0 && pointIndex < dataRows.Count)
{
DataRow rowToDelete = dataRows[pointIndex];
rowToDelete.Delete(); // Удаление строки из DataTable
// Удаление точки из ScatterSeries и списка DataRows
selectedSeries.Points.RemoveAt(pointIndex);
dataRows.RemoveAt(pointIndex);
// Обновление графика
MainPlotView.Model.InvalidatePlot(true);
}
}
// Сброс выбранной серии и точки
selectedSeries = null;
selectedPoint = null;
}
}
```
### 4. Обновите обработчик MouseDown для PlotView
Убедитесь, что обработчик `PlotView_MouseDown` корректно устанавливает выбранную серию и точку для последующего удаления.
```csharp
private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
{
var plotModel = MainPlotView.Model;
if (plotModel == null)
return;
// Получить позицию клика относительно PlotView
var mousePos = e.GetPosition(MainPlotView);
// Преобразование координат клика в данные координаты
double xData, yData;
try
{
xData = plotModel.DefaultXAxis.InverseTransform(mousePos.X);
yData = plotModel.DefaultYAxis.InverseTransform(mousePos.Y);
}
catch
{
// Если преобразование не удалось, выйти
return;
}
// Определить ближайшую точку и расстояние до неё (в пикселях)
double thresholdInPixels = 10; // Пороговое расстояние в пикселях
double minDistance = double.MaxValue;
ScatterSeries nearestSeries = null;
ScatterPoint nearestPoint = null;
foreach (var series in plotModel.Series)
{
if (series is ScatterSeries scatterSeries)
{
foreach (var point in scatterSeries.Points)
{
// Перевести данные координаты в экранные
double screenX = plotModel.DefaultXAxis.Transform(point.X);
double screenY = plotModel.DefaultYAxis.Transform(point.Y);
// Вычисление расстояния между кликом и точкой
double distance = Math.Sqrt(Math.Pow(mousePos.X - screenX, 2) + Math.Pow(mousePos.Y - screenY, 2));
if (distance < minDistance && distance <= thresholdInPixels)
{
minDistance = distance;
nearestSeries = scatterSeries;
nearestPoint = point;
}
}
}
}
if (nearestSeries != null && nearestPoint != null)
{
// Сохранить выбранную серию и точку
selectedSeries = nearestSeries;
selectedPoint = nearestPoint;
// Показать контекстное меню в позиции клика
// Convert mouse position relative to window
var windowPos = this.PointToScreen(mousePos);
ShowContextMenu(new System.Windows.Point(windowPos.X, windowPos.Y));
// Обработка события завершена
e.Handled = true;
}
}
```
### 5. Обеспечьте обновление `GraphWindow` после удаления точек
Если необходимо, добавьте методы для обновления графиков после удаления точек. Однако в данном случае вызов `InvalidatePlot(true)` уже обновляет график.
## Изменения в `MainWindow.xaml.cs`
В большинстве случаев дополнительных изменений в `MainWindow.xaml.cs` не требуется, поскольку `DataGrid` привязан к `DataTable` и автоматически обновляется при изменении данных. Однако, для полной уверенности можно вызвать обновление `TabControl` после закрытия `GraphWindow`. Для этого можно модифицировать метод `ShowGraphs_Click` следующим образом:
```csharp
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)
{
Owner = this
};
graphWindow.ShowDialog();
// После закрытия GraphWindow обновить TabControl с объединёнными данными
if (combinedTxtDataTable != null)
{
AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", FilesTabControl);
}
if (combinedMagneteDataTable != null)
{
AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
}
StatusTextBlock.Text = $"Данные обновлены после редактирования графиков.";
}
```
Этот код удостоверяется, что после закрытия окна графиков объединённые данные обновляются и отображаются в `MainWindow`.
## Итоговый полный код `GraphWindow.xaml.cs` с изменениями
Для удобства, ниже представлен полный обновленный файл `GraphWindow.xaml.cs` с внесенными изменениями:
```csharp
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
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; }
// Сопоставление ScatterSeries с соответствующими DataRow
private Dictionary<ScatterSeries, List<DataRow>> seriesDataRows = new Dictionary<ScatterSeries, List<DataRow>>();
// Ссылки на выбранную серию и точку
private ScatterSeries selectedSeries = null;
private ScatterPoint selectedPoint = null;
// Конструктор класса
public GraphWindow(DataTable txtData, DataTable magneteData)
{
InitializeComponent();
InitializePlotModels(txtData, magneteData); // Инициализация графиков
this.DataContext = this;
// Подписка на событие нажатия мыши на PlotView
MainPlotView.MouseDown += PlotView_MouseDown;
}
/// <summary>
/// Устанавливает равный масштаб для осей X и Y.
/// </summary>
private void SetEqualAxisScaling(LinearAxis xAxis, LinearAxis yAxis, IEnumerable<DataRow> dataRows, string xColumn, string yColumn)
{
try
{
// Вычисление минимальных и максимальных значений
double minX = dataRows.Min(row => Convert.ToDouble(row[xColumn]));
double maxX = dataRows.Max(row => Convert.ToDouble(row[xColumn]));
double minY = dataRows.Min(row => Convert.ToDouble(row[yColumn]));
double maxY = dataRows.Max(row => Convert.ToDouble(row[yColumn]));
double rangeX = maxX - minX;
double rangeY = maxY - minY;
// Определение максимального диапазона
double maxRange = Math.Max(rangeX, rangeY);
// Вычисление центра диапазона
double centerX = (minX + maxX) / 2;
double centerY = (minY + maxY) / 2;
// Установка диапазонов осей для равного масштаба
xAxis.Minimum = centerX - maxRange / 2;
xAxis.Maximum = centerX + maxRange / 2;
yAxis.Minimum = centerY - maxRange / 2;
yAxis.Maximum = centerY + maxRange / 2;
// Установка основных шагов
xAxis.MajorStep = maxRange / 10;
yAxis.MajorStep = maxRange / 10;
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при установке масштаба осей: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Инициализирует модели графиков.
/// </summary>
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 ScatterSeries
{
Title = "BASE-1",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nBASE-1: {4}"
};
List<DataRow> txtBase1DataRows = new List<DataRow>();
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))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtBase1Series.Points.Add(new ScatterPoint(timeInDouble, base1));
txtBase1DataRows.Add(row);
}
}
TxtBase1Model.Series.Add(txtBase1Series);
seriesDataRows[txtBase1Series] = txtBase1DataRows;
// График 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 ScatterSeries
{
Title = "Noise",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nNoise: {4}"
};
List<DataRow> txtNoiseDataRows = new List<DataRow>();
foreach (DataRow row in txtData.Rows)
{
if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
double.TryParse(row["Noise"].ToString(), out double noise))
{
double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
txtNoiseSeries.Points.Add(new ScatterPoint(timeInDouble, noise));
txtNoiseDataRows.Add(row);
}
}
TxtNoiseModel.Series.Add(txtNoiseSeries);
seriesDataRows[txtNoiseSeries] = txtNoiseDataRows;
// График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
var xAxisAltitude = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisAltitude = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteAltitudeModel.Axes.Add(xAxisAltitude);
MagneteAltitudeModel.Axes.Add(yAxisAltitude);
var colorAxisAltitude = new LinearColorAxis
{
Position = AxisPosition.Right,
Palette = OxyPalettes.Jet(500),
Title = "Альтитуда"
};
MagneteAltitudeModel.Axes.Add(colorAxisAltitude);
var magneteAltitudeSeries = new ScatterSeries
{
Title = "Альтитуда",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nАльтитуда: {4}"
};
List<DataRow> magneteAltitudeDataRows = new List<DataRow>();
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));
magneteAltitudeDataRows.Add(row);
}
}
MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);
seriesDataRows[magneteAltitudeSeries] = magneteAltitudeDataRows;
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisAltitude, yAxisAltitude, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// График 4: .magnete Долгота UTM vs Широта UTM (Shark)
MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
var xAxisShark = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "Долгота UTM"
};
var yAxisShark = new LinearAxis
{
Position = AxisPosition.Left,
Title = "Широта UTM"
};
MagneteSharkModel.Axes.Add(xAxisShark);
MagneteSharkModel.Axes.Add(yAxisShark);
var colorAxisShark = new LinearColorAxis
{
Position = AxisPosition.Right,
Palette = OxyPalettes.Jet(500),
Title = "Shark"
};
MagneteSharkModel.Axes.Add(colorAxisShark);
var magneteSharkSeries = new ScatterSeries
{
Title = "Shark",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nShark: {4}"
};
List<DataRow> magneteSharkDataRows = new List<DataRow>();
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));
magneteSharkDataRows.Add(row);
}
}
MagneteSharkModel.Series.Add(magneteSharkSeries);
seriesDataRows[magneteSharkSeries] = magneteSharkDataRows;
// Установка равного масштаба для осей
SetEqualAxisScaling(xAxisShark, yAxisShark, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");
// Настройка PlotView по умолчанию
MainPlotView.Model = TxtBase1Model;
}
/// <summary>
/// Обработчик выбора графика из ComboBox.
/// </summary>
private void ChartSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItem = (sender as ComboBox)?.SelectedItem as ComboBoxItem;
if (selectedItem != null)
{
switch (selectedItem.Content.ToString())
{
case ".txt Time UTC vs BASE-1":
MainPlotView.Model = TxtBase1Model;
break;
case ".txt Time UTC vs Noise":
MainPlotView.Model = TxtNoiseModel;
break;
case ".magnete Долгота UTM vs Широта UTM (Альтитуда)":
MainPlotView.Model = MagneteAltitudeModel;
break;
case ".magnete Долгота UTM vs Широта UTM (Shark)":
MainPlotView.Model = MagneteSharkModel;
break;
default:
break;
}
}
}
/// <summary>
/// Обработчик события MouseDown для PlotView.
/// </summary>
private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
{
var plotModel = MainPlotView.Model;
if (plotModel == null)
return;
// Получить позицию клика относительно PlotView
var mousePos = e.GetPosition(MainPlotView);
// Преобразование координат клика в данные координаты
double xData, yData;
try
{
xData = plotModel.DefaultXAxis.InverseTransform(mousePos.X);
yData = plotModel.DefaultYAxis.InverseTransform(mousePos.Y);
}
catch
{
// Если преобразование не удалось, выйти
return;
}
// Определить ближайшую точку и расстояние до неё (в пикселях)
double thresholdInPixels = 10; // Пороговое расстояние в пикселях
double minDistance = double.MaxValue;
ScatterSeries nearestSeries = null;
ScatterPoint nearestPoint = null;
foreach (var series in plotModel.Series)
{
if (series is ScatterSeries scatterSeries)
{
foreach (var point in scatterSeries.Points)
{
// Перевести данные координаты в экранные
double screenX = plotModel.DefaultXAxis.Transform(point.X);
double screenY = plotModel.DefaultYAxis.Transform(point.Y);
// Вычисление расстояния между кликом и точкой
double distance = Math.Sqrt(Math.Pow(mousePos.X - screenX, 2) + Math.Pow(mousePos.Y - screenY, 2));
if (distance < minDistance && distance <= thresholdInPixels)
{
minDistance = distance;
nearestSeries = scatterSeries;
nearestPoint = point;
}
}
}
}
if (nearestSeries != null && nearestPoint != null)
{
// Сохранить выбранную серию и точку
selectedSeries = nearestSeries;
selectedPoint = nearestPoint;
// Показать контекстное меню в позиции клика
// Convert mouse position relative to window
var windowPos = this.PointToScreen(mousePos);
ShowContextMenu(new System.Windows.Point(windowPos.X, windowPos.Y));
// Обработка события завершена
e.Handled = true;
}
}
/// <summary>
/// Отображает контекстное меню с опцией удаления точки.
/// </summary>
/// <param name="position">Позиция клика для отображения меню (в координатах экрана).</param>
private void ShowContextMenu(System.Windows.Point position)
{
if (selectedSeries == null || selectedPoint == null)
return;
// Создание контекстного меню
ContextMenu contextMenu = new ContextMenu();
MenuItem deleteItem = new MenuItem
{
Header = "Удалить точку"
};
deleteItem.Click += DeleteItem_Click;
contextMenu.Items.Add(deleteItem);
// Установка позиции контекстного меню
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.AbsolutePoint;
contextMenu.HorizontalOffset = position.X;
contextMenu.VerticalOffset = position.Y;
// Открытие контекстного меню
contextMenu.IsOpen = true;
}
/// <summary>
/// Обработчик события удаления точки из контекстного меню.
/// </summary>
private void DeleteItem_Click(object sender, RoutedEventArgs e)
{
if (selectedSeries != null && selectedPoint != null)
{
var result = MessageBox.Show("Вы уверены, что хотите удалить эту точку?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
// Найти соответствующую DataRow
var dataRows = seriesDataRows[selectedSeries];
int pointIndex = selectedSeries.Points.IndexOf(selectedPoint);
if (pointIndex >= 0 && pointIndex < dataRows.Count)
{
DataRow rowToDelete = dataRows[pointIndex];
rowToDelete.Delete(); // Удаление строки из DataTable
// Удаление точки из ScatterSeries и списка DataRows
selectedSeries.Points.RemoveAt(pointIndex);
dataRows.RemoveAt(pointIndex);
// Обновление графика
MainPlotView.Model.InvalidatePlot(true);
}
}
// Сброс выбранной серии и точки
selectedSeries = null;
selectedPoint = null;
}
}
}
}
```
## Проверка и обновление `MainWindow.xaml.cs`
Убедитесь, что после закрытия окна `GraphWindow` объединённые `DataTable` обновлены и отображаются в `MainWindow`. Если необходимо, вы можете вызвать обновление вкладок после закрытия `GraphWindow`. Например, в методе `ShowGraphs_Click` добавьте обновление вкладок после закрытия графиков:
```csharp
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)
{
Owner = this
};
graphWindow.ShowDialog();
// После закрытия GraphWindow обновить TabControl с объединёнными данными
if (combinedTxtDataTable != null)
{
AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", FilesTabControl);
}
if (combinedMagneteDataTable != null)
{
AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
}
StatusTextBlock.Text = $"Данные обновлены после редактирования графиков.";
}
```
Однако, если ваши `DataGrid` привязаны к `DataTable.DefaultView`, обновления должны происходить автоматически без необходимости повторного добавления вкладок. В этом случае данный код может быть опущен.
## Сохранение изменений
После внесения изменений в графики и удаления точек, убедитесь, что вы сохраняете проект, чтобы изменения были сохранены. Для этого используйте существующую функциональность сохранения проекта (`SaveProject_Click`), которая сериализует `ProjectData`, включая изменённые `DataTable`.
## Проверка работоспособности
1. **Удаление точки**: Откройте окно графиков (`GraphWindow`), выберите график и удалите точку, кликнув по ней правой кнопкой мыши и выбрав "Удалить точку".
2. **Сохранение проекта**: После удаления точек сохраните проект, используя кнопку "Сохранить проект" в `MainWindow`.
3. **Загрузка проекта**: Перезагрузите проект или убедитесь, что изменённые данные отображаются в `MainWindow` после закрытия `GraphWindow`.
Теперь при каждом удалении точки из графиков изменения будут отражены в соответствующих `DataTable`, и при сохранении проекта они будут сохранены. При загрузке проекта или обновлении данных в `MainWindow` обновлённые данные будут отображены корректно.
Если у вас возникнут дополнительные вопросы или проблемы с реализацией, не стесняйтесь обращаться за помощью!