S7Explorer/ViewModels/MainViewModel.cs

421 lines
15 KiB
C#

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<S7Object> _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);
}
/// <summary>
/// Actualiza la información de interfaz de una función seleccionada
/// </summary>
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<S7Object> 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<string> RefreshProjectCommand { get; } // Cambiado a string
public IRelayCommand CancelLoadingCommand { get; }
public MainViewModel()
{
ProjectStructure = new ObservableCollection<S7Object>();
OpenProjectCommand = new RelayCommand(OpenProject);
SearchCommand = new RelayCommand(SearchInProject);
ClearLogCommand = new RelayCommand(ClearLog);
RefreshProjectCommand = new RelayCommand<string>(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<S7Object>();
// 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<S7Object> 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
}
}