474 lines
17 KiB
C#
474 lines
17 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 IRelayCommand DiagnoseProjectCommand { 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);
|
|
DiagnoseProjectCommand = new RelayCommand(DiagnoseProject);
|
|
|
|
_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 DiagnoseProject()
|
|
{
|
|
if (_currentProject == null)
|
|
{
|
|
LogInfo("No hay proyecto para diagnosticar.");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
LogInfo("Iniciando diagnóstico del proyecto...");
|
|
|
|
// Explorar archivos principales
|
|
string projectDir = Path.GetDirectoryName(_currentProject.FilePath);
|
|
|
|
// S7RESOFF.DBF
|
|
string s7resoffPath = Path.Combine(projectDir, "hrs", "S7RESOFF.DBF");
|
|
if (File.Exists(s7resoffPath))
|
|
{
|
|
LogInfo("Diagnosticando S7RESOFF.DBF:");
|
|
DbfParser.LogColumnInfo(s7resoffPath, message => LogInfo(message));
|
|
}
|
|
|
|
// SYMLISTS.DBF
|
|
string symlistsPath = Path.Combine(projectDir, "YDBs", "SYMLISTS.DBF");
|
|
if (File.Exists(symlistsPath))
|
|
{
|
|
LogInfo("Diagnosticando SYMLISTS.DBF:");
|
|
DbfParser.LogColumnInfo(symlistsPath, message => LogInfo(message));
|
|
}
|
|
|
|
// Buscar un SUBBLK.DBF
|
|
foreach (var dir in Directory.GetDirectories(Path.Combine(projectDir, "ombstx", "offline")))
|
|
{
|
|
string subblkPath = Path.Combine(dir, "SUBBLK.DBF");
|
|
if (File.Exists(subblkPath))
|
|
{
|
|
LogInfo($"Diagnosticando SUBBLK.DBF en {dir}:");
|
|
DbfParser.LogColumnInfo(subblkPath, message => LogInfo(message));
|
|
break; // Solo el primero para no saturar
|
|
}
|
|
}
|
|
|
|
LogInfo("Diagnóstico completado.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"Error durante el diagnóstico: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
} |