using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Ookii.Dialogs.Wpf; using S7Explorer.Models; using S7Explorer.Parsers; using S7Explorer.Services; namespace S7Explorer.ViewModels { public class MainViewModel : ObservableObject { private string _searchText; private object _selectedObject; private object _selectedTreeItem; private S7Project _currentProject; private ObservableCollection _projectStructure; private string _projectInfo; private bool _isLoading; private string _logText; private bool _useCache = true; private S7ParserService _parserService; public string SearchText { get => _searchText; set => SetProperty(ref _searchText, value); } public object SelectedObject { get => _selectedObject; set => SetProperty(ref _selectedObject, value); } public object SelectedTreeItem { get => _selectedTreeItem; set { if (SetProperty(ref _selectedTreeItem, value)) { // Actualizar el objeto seleccionado para PropertyGrid SelectedObject = _selectedTreeItem; // Acciones específicas según el tipo de objeto seleccionado if (_selectedTreeItem is S7Function fc) { // Si el objeto seleccionado es una función, actualizar su interfaz UpdateFunctionInterface(fc); } } } } public string LogText { get => _logText; set => SetProperty(ref _logText, value); } public bool UseCache { get => _useCache; set => SetProperty(ref _useCache, value); } /// /// Actualiza la información de interfaz de una función seleccionada /// private void UpdateFunctionInterface(S7Function fc) { try { // Aquí podríamos cargar información adicional específica de la función // Por ejemplo, cargar el código de la función, detalles de parámetros, etc. // Este método se ejecutará cuando el usuario seleccione una FC en el árbol LogInfo($"Seleccionada función: {fc.Name} ({fc.Number})"); } catch (Exception ex) { LogError($"Error al actualizar interfaz de función: {ex.Message}"); } } public ObservableCollection ProjectStructure { get => _projectStructure; set => SetProperty(ref _projectStructure, value); } public string ProjectInfo { get => _projectInfo; set => SetProperty(ref _projectInfo, value); } public bool IsLoading { get => _isLoading; set => SetProperty(ref _isLoading, value); } public IRelayCommand OpenProjectCommand { get; } public IRelayCommand SearchCommand { get; } public IRelayCommand ClearLogCommand { get; } public IRelayCommand RefreshProjectCommand { get; } // Cambiado a string public IRelayCommand CancelLoadingCommand { get; } public MainViewModel() { ProjectStructure = new ObservableCollection(); OpenProjectCommand = new RelayCommand(OpenProject); SearchCommand = new RelayCommand(SearchInProject); ClearLogCommand = new RelayCommand(ClearLog); RefreshProjectCommand = new RelayCommand(RefreshProject); // Cambiado a string CancelLoadingCommand = new RelayCommand(CancelLoading); _parserService = new S7ParserService(); _parserService.LogEvent += OnParserLogEvent; _parserService.StructureUpdatedEvent += OnStructureUpdated; ProjectInfo = "No hay proyecto abierto"; LogText = "S7 Project Explorer iniciado. Versión 1.0\r\n"; } private void RefreshProject(string useCacheString) { // Convertir el string a booleano bool useCache = useCacheString == "True"; if (_currentProject != null) { UseCache = useCache; LoadProjectAsync(_currentProject.FilePath); } } private void OpenProject() { var dialog = new VistaOpenFileDialog { Title = "Seleccionar archivo de proyecto STEP7", Filter = "Proyectos STEP7 (*.s7p)|*.s7p|Todos los archivos (*.*)|*.*", CheckFileExists = true }; if (dialog.ShowDialog() == true) { LoadProjectAsync(dialog.FileName); } } private void RefreshProject(bool useCache) { if (_currentProject != null) { UseCache = useCache; LoadProjectAsync(_currentProject.FilePath); } } private void CancelLoading() { try { if (IsLoading) { LogWarning("Cancelando operación de carga por petición del usuario..."); _parserService.CancelParsing(); } } catch (Exception ex) { LogError($"Error al cancelar la operación: {ex.Message}"); } } private async void LoadProjectAsync(string filePath) { try { IsLoading = true; ProjectInfo = "Cargando proyecto..."; LogInfo($"Iniciando carga del proyecto: {filePath}"); // Reiniciar estado ProjectStructure.Clear(); _currentProject = null; // Parsear proyecto _currentProject = await _parserService.ParseProjectAsync(filePath, UseCache); // Actualizar UI ProjectStructure.Add(_currentProject); // Expandir nodo raíz si no estamos cargando desde caché (ya que la caché preserva el estado de expansión) if (!UseCache) { _currentProject.IsExpanded = true; } // Actualizar info string projectName = Path.GetFileNameWithoutExtension(filePath); ProjectInfo = $"Proyecto: {projectName}"; LogInfo($"Proyecto cargado: {projectName}"); } catch (OperationCanceledException) { // La operación fue cancelada ProjectInfo = "Carga cancelada"; LogWarning("Operación de carga cancelada por el usuario"); } catch (Exception ex) { MessageBox.Show($"Error al cargar el proyecto: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); ProjectInfo = "Error al cargar el proyecto"; LogError($"Error al cargar proyecto: {ex.Message}"); if (ex.InnerException != null) { LogError($" Detalle: {ex.InnerException.Message}"); } } finally { IsLoading = false; } } private void OnParserLogEvent(object sender, string message) { // Este evento se desencadena en un hilo de fondo, así que aseguramos // que se ejecute en el hilo de la UI Application.Current.Dispatcher.Invoke(() => { LogText += message + Environment.NewLine; }); } private void OnStructureUpdated(object sender, S7Object updatedObject) { // Este evento ya debe estar en el hilo de la UI // No necesitamos hacer nada especial aquí, ya que la actualización // se refleja automáticamente en el árbol gracias a la implementación de INPC // y ObservableCollection } private void SearchInProject() { if (_currentProject == null || string.IsNullOrWhiteSpace(SearchText)) return; try { LogInfo($"Buscando: \"{SearchText}\""); // Recopilar todos los objetos en el proyecto var allObjects = GetAllObjects(_currentProject); // Lista para almacenar coincidencias var matchingObjects = new List(); // Buscar en función del texto de búsqueda string searchText = SearchText.Trim().ToLowerInvariant(); // Buscar objetos que coincidan con el texto foreach (var obj in allObjects) { // Comprobar si el texto de búsqueda parece ser una referencia a FC específica if (searchText.StartsWith("fc") && searchText.Length >= 3) { // Si es una función específica (por ejemplo "fc5") if (obj is S7Function func && func.Number?.ToLowerInvariant() == searchText) { matchingObjects.Add(func); } } // Comprobar si queremos encontrar todas las FCs else if (searchText == "fc" || searchText == "función" || searchText == "funcion") { if (obj is S7Function) { matchingObjects.Add(obj); } } // Búsqueda en parámetros de función else if (obj is S7Function func) { if (func.ContainsText(searchText)) { matchingObjects.Add(func); } else if (func.Parameters != null) { // Buscar en los parámetros de la función foreach (var param in func.Parameters) { if (param.Name?.ToLowerInvariant().Contains(searchText) == true || param.DataType?.ToLowerInvariant().Contains(searchText) == true || (param.Description?.ToLowerInvariant().Contains(searchText) == true)) { matchingObjects.Add(func); break; } } } } // Búsqueda general para otros objetos else if (obj.ContainsText(searchText)) { matchingObjects.Add(obj); } } if (matchingObjects.Count == 0) { LogInfo($"No se encontraron coincidencias para: \"{SearchText}\""); MessageBox.Show($"No se encontraron coincidencias para: \"{SearchText}\"", "Búsqueda", MessageBoxButton.OK, MessageBoxImage.Information); return; } // Seleccionar el primer objeto coincidente y expandir su ruta var firstMatch = matchingObjects.First(); SelectAndExpandToObject(firstMatch); LogInfo($"Encontrado: {firstMatch.Name} ({firstMatch.ObjectType})"); // Informar al usuario if (matchingObjects.Count > 1) { LogInfo($"Se encontraron {matchingObjects.Count} coincidencias en total."); MessageBox.Show($"Se encontraron {matchingObjects.Count} coincidencias. " + "Mostrando la primera coincidencia.", "Búsqueda", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { LogError($"Error durante la búsqueda: {ex.Message}"); MessageBox.Show($"Error durante la búsqueda: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private IEnumerable GetAllObjects(S7Object root) { // Devolver el objeto raíz yield return root; // Recursivamente devolver todos los hijos foreach (var child in root.Children) { foreach (var obj in GetAllObjects(child)) { yield return obj; } } } private void SelectAndExpandToObject(S7Object obj) { // Expandir todos los nodos padres hasta llegar al objeto var parent = obj.Parent; while (parent != null) { parent.IsExpanded = true; parent = parent.Parent; } // Seleccionar el objeto SelectedTreeItem = obj; } private void ClearLog() { LogText = string.Empty; LogInfo("Log limpiado por el usuario"); } #region Logging Methods public void LogInfo(string message) { string timestamp = DateTime.Now.ToString("HH:mm:ss"); LogText += $"[{timestamp}] INFO: {message}{Environment.NewLine}"; } public void LogWarning(string message) { string timestamp = DateTime.Now.ToString("HH:mm:ss"); LogText += $"[{timestamp}] ADVERTENCIA: {message}{Environment.NewLine}"; } public void LogError(string message) { string timestamp = DateTime.Now.ToString("HH:mm:ss"); LogText += $"[{timestamp}] ERROR: {message}{Environment.NewLine}"; } public void LogDebug(string message) { string timestamp = DateTime.Now.ToString("HH:mm:ss"); LogText += $"[{timestamp}] DEBUG: {message}{Environment.NewLine}"; } #endregion } }