diff --git a/MainViewModel.cs b/MainViewModel.cs index 89efb45..92ef191 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -1011,11 +1011,15 @@ namespace CtrEditor _objectManager.UpdateSelectionVisuals(); } } + public class SimulationData { public ObservableCollection? ObjetosSimulables { get; set; } public UnitConverter? UnitConverter { get; set; } public PLCViewModel? PLC_ConnectionData { get; set; } + + // Nueva propiedad para almacenar los datos locales de objetos globales + public ObservableCollection? DatosLocalesObjetos { get; set; } } public class TipoSimulable diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index 98e4f0c..774da08 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CtrEditor.FuncionesBase; +using CtrEditor.Serialization; using CtrEditor.Simulacion; using LibS7Adv; using nkast.Aether.Physics2D.Common; @@ -449,10 +450,151 @@ namespace CtrEditor.ObjetosSim [property: Category("Layout:")] private bool enable_On_All_Pages; + partial void OnEnable_On_All_PagesChanged(bool value) { + // Si se está desactivando el modo global if (!value) + { + // Limpiar los datos locales + _datosLocales = null; + _snapshotGlobal = null; + + // Desactivar datos locales + Enable_Local_Data = false; + Show_On_This_Page = true; + } + } + + + // Local Data for Global Objects + [NotifyPropertyChangedFor(nameof(Show_On_This_Page))] + [ObservableProperty] + [property: Description("Enable local data of this global Object on all pages.")] + [property: Category("Layout:")] + private bool enable_Local_Data_for_All; + + partial void OnEnable_Local_Data_for_AllChanged(bool value) + { + if (value) + { + // Activar datos locales + Enable_Local_Data = true; + } + } + + // Local Data for Global Objects + [NotifyPropertyChangedFor(nameof(Show_On_This_Page))] + [ObservableProperty] + [property: Description("Enable local data of this global Object on this page.")] + [property: Category("Layout:")] + [property: JsonIgnore] + private bool enable_Local_Data; + + // Añadir estas propiedades y métodos a la clase osBase + + [JsonIgnore] + private DatosLocales _snapshotGlobal; + + [JsonIgnore] + private DatosLocales _datosLocales; + + /// + /// Crea un snapshot de los datos globales actuales + /// + public void CrearSnapshotGlobal() + { + if (Enable_On_All_Pages) // No verificamos Enable_Local_Data aquí + { + _snapshotGlobal = new DatosLocales(Id); + _snapshotGlobal.CopiarDesdeObjeto(this); + } + } + + /// + /// Restaura los datos globales desde el snapshot + /// + public void RestaurarDesdeSnapshotGlobal() + { + if (Enable_On_All_Pages && Enable_Local_Data && _snapshotGlobal != null) + { + // Guarda los datos locales actuales antes de restaurar + if (_datosLocales == null) + { + _datosLocales = new DatosLocales(Id); + } + _datosLocales.CopiarDesdeObjeto(this); + + // Restaura desde el snapshot + _snapshotGlobal.AplicarAObjeto(this); + } + } + + /// + /// Restaura los datos locales después de guardar + /// + public void RestaurarDatosLocales() + { + if (Enable_On_All_Pages && Enable_Local_Data && _datosLocales != null) + { + _datosLocales.AplicarAObjeto(this); + } + } + + /// + /// Obtiene los datos locales del objeto + /// + public DatosLocales ObtenerDatosLocales() + { + if (!Enable_On_All_Pages) + return null; + + DatosLocales datos = new DatosLocales(Id); + datos.CopiarDesdeObjeto(this); + return datos; + } + + /// + /// Aplica datos locales al objeto + /// + public void AplicarDatosLocales(DatosLocales datos) + { + if (Enable_On_All_Pages && datos != null && datos.Id_GlobalObject.Value == Id.Value) + { + Enable_Local_Data = true; // Activar datos locales si se encuentran + + datos.AplicarAObjeto(this); + + // Actualiza la visualización + ActualizarLeftTop(); + OnAnchoChanged(Ancho); + OnAltoChanged(Alto); + } + } + + partial void OnEnable_Local_DataChanged(bool value) + { + // Si se está desactivando los datos locales y teníamos un snapshot + if (!value && _snapshotGlobal != null && Enable_On_All_Pages) + { + // Restaurar los valores globales + _snapshotGlobal.AplicarAObjeto(this); + + // Limpiar los datos locales + _datosLocales = null; + _snapshotGlobal = null; + + // Actualizar la visualización + ActualizarLeftTop(); + OnAnchoChanged(Ancho); + OnAltoChanged(Alto); + } + // Si se está activando, crear el snapshot + else if (value && Enable_On_All_Pages) + { + CrearSnapshotGlobal(); + } } [ObservableProperty] diff --git a/Serialization/DatosLocales.cs b/Serialization/DatosLocales.cs new file mode 100644 index 0000000..1e13b40 --- /dev/null +++ b/Serialization/DatosLocales.cs @@ -0,0 +1,91 @@ +using CtrEditor.ObjetosSim; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace CtrEditor.Serialization +{ + /// + /// Almacena datos locales para un objeto global + /// + public class DatosLocales + { + /// + /// ID del objeto global al que pertenecen estos datos locales + /// + public UniqueId Id_GlobalObject { get; set; } + + /// + /// Posición vertical local + /// + public float? Top { get; set; } + + /// + /// Posición horizontal local + /// + public float? Left { get; set; } + + /// + /// Ancho local + /// + public float? Ancho { get; set; } + + /// + /// Alto local + /// + public float? Alto { get; set; } + + /// + /// Diccionario para almacenar propiedades dinámicas adicionales + /// + public Dictionary PropiedadesAdicionales { get; set; } + + /// + /// Constructor por defecto necesario para deserialización + /// + public DatosLocales() + { + PropiedadesAdicionales = new Dictionary(); + } + + /// + /// Constructor con ID del objeto global + /// + public DatosLocales(UniqueId idGlobalObject) + { + Id_GlobalObject = idGlobalObject; + PropiedadesAdicionales = new Dictionary(); + } + + /// + /// Copia los datos relevantes desde un objeto global + /// + public void CopiarDesdeObjeto(osBase objeto) + { + if (objeto == null) return; + + Id_GlobalObject = objeto.Id; + Top = objeto.Top; + Left = objeto.Left; + Ancho = objeto.Ancho; + Alto = objeto.Alto; + + // Aquí puedes añadir más propiedades si es necesario + } + + /// + /// Aplica los datos locales al objeto global + /// + public void AplicarAObjeto(osBase objeto) + { + if (objeto == null || objeto.Id.Value != Id_GlobalObject.Value) return; + + if (Left.HasValue) objeto.Left = Left.Value; + if (Top.HasValue) objeto.Top = Top.Value; + if (Ancho.HasValue) objeto.Ancho = Ancho.Value; + if (Alto.HasValue) objeto.Alto = Alto.Value; + + // Aquí puedes aplicar más propiedades si es necesario + } + } +} \ No newline at end of file diff --git a/Serialization/PropiedadesLocalesManager.cs b/Serialization/PropiedadesLocalesManager.cs new file mode 100644 index 0000000..c6f6ccf --- /dev/null +++ b/Serialization/PropiedadesLocalesManager.cs @@ -0,0 +1,128 @@ +using CtrEditor.ObjetosSim; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Reflection; + +namespace CtrEditor.Serialization +{ + /// + /// Clase para gestionar qué propiedades deben ser locales en objetos globales + /// + public static class PropiedadesLocalesManager + { + // Definición de propiedades por defecto que se guardan localmente para todos los objetos + private static readonly List PropiedadesPorDefecto = new List + { + "Left", "Top", "Ancho", "Alto" + }; + + // Diccionario que contiene conjuntos de propiedades locales por tipo de objeto + private static readonly Dictionary> PropiedadesPorTipo = new Dictionary>(); + + /// + /// Registra un conjunto de propiedades locales para un tipo específico de objeto + /// + /// Tipo de objeto (debe heredar de osBase) + /// Lista de nombres de propiedades a registrar como locales + public static void RegistrarPropiedadesParaTipo(List propiedades) where T : osBase + { + PropiedadesPorTipo[typeof(T)] = propiedades; + } + + /// + /// Obtiene la lista de propiedades que deben ser locales para un objeto concreto + /// + /// Objeto del que obtener las propiedades locales + /// Lista de nombres de propiedades + public static List ObtenerPropiedadesLocales(osBase objeto) + { + var resultado = new List(PropiedadesPorDefecto); + + if (objeto != null && PropiedadesPorTipo.TryGetValue(objeto.GetType(), out var propiedadesExtra)) + { + foreach (var prop in propiedadesExtra) + { + if (!resultado.Contains(prop)) + resultado.Add(prop); + } + } + + return resultado; + } + + /// + /// Crea un objeto dinámico que permite acceder a las propiedades locales de un objeto + /// + /// Objeto del que crear una vista dinámica + /// Objeto dinámico con propiedades locales + public static dynamic CrearAccesoDinamico(osBase objeto) + { + return new PropiedadesLocalesDinamicas(objeto); + } + + /// + /// Clase interna que implementa un acceso dinámico a propiedades + /// + private class PropiedadesLocalesDinamicas : DynamicObject + { + private readonly osBase _objeto; + private readonly List _propiedadesLocales; + + public PropiedadesLocalesDinamicas(osBase objeto) + { + _objeto = objeto; + _propiedadesLocales = ObtenerPropiedadesLocales(objeto); + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + var nombrePropiedad = binder.Name; + + if (_propiedadesLocales.Contains(nombrePropiedad)) + { + var propInfo = _objeto.GetType().GetProperty(nombrePropiedad); + if (propInfo != null && propInfo.CanRead) + { + result = propInfo.GetValue(_objeto); + return true; + } + } + + result = null; + return false; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + var nombrePropiedad = binder.Name; + + if (_propiedadesLocales.Contains(nombrePropiedad)) + { + var propInfo = _objeto.GetType().GetProperty(nombrePropiedad); + if (propInfo != null && propInfo.CanWrite) + { + try + { + var valorConvertido = Convert.ChangeType(value, propInfo.PropertyType); + propInfo.SetValue(_objeto, valorConvertido); + return true; + } + catch (Exception) + { + // Silenciar errores de conversión + } + } + } + + return false; + } + + public override IEnumerable GetDynamicMemberNames() + { + return _propiedadesLocales; + } + } + } +} \ No newline at end of file diff --git a/Serialization/StateSerializer.cs b/Serialization/StateSerializer.cs index ad83bc3..d247147 100644 --- a/Serialization/StateSerializer.cs +++ b/Serialization/StateSerializer.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Windows; using CtrEditor.Simulacion; using System.IO; +using System.Linq; namespace CtrEditor.Serialization { @@ -30,36 +31,68 @@ namespace CtrEditor.Serialization var objetosSimulables = new ObservableCollection(); var objetosSimulablesAllPages = new ObservableCollection(); + var datosLocalesObjetos = new ObservableCollection(); + // Paso 1: Preparar objetos globales y locales foreach (var obj in _mainViewModel.ObjetosSimulables) { + // Para objetos globales con datos locales + if (obj.Enable_On_All_Pages && obj.Enable_Local_Data) + { + // Guarda los datos locales independientemente de Show_On_This_Page + var datosLocales = obj.ObtenerDatosLocales(); + if (datosLocales != null) + { + datosLocalesObjetos.Add(datosLocales); + } + + // Restaura datos globales para guardar + obj.RestaurarDesdeSnapshotGlobal(); + } + + // Prepara para serialización obj.SalvarDatosNoSerializables(); + + // Separa objetos globales y locales if (!obj.Enable_On_All_Pages) + { objetosSimulables.Add(obj); + } else + { objetosSimulablesAllPages.Add(obj); + } } - // Save current page objects + // Paso 2: Guardar datos de la página actual var dataToSerialize = new SimulationData { - ObjetosSimulables = objetosSimulables, + ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null, UnitConverter = PixelToMeter.Instance.calc, - PLC_ConnectionData = _mainViewModel.PLCViewModel + PLC_ConnectionData = _mainViewModel.PLCViewModel, + DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null }; var path = _datosDeTrabajo.ObtenerPathImagenConExtension(selectedImage, ".json"); if (path != null) SerializeAndSave(dataToSerialize, path); - // Save all pages objects + // Paso 3: Guardar objetos globales path = _datosDeTrabajo.ObtenerPathAllPages(".json"); if (path != null) SerializeAndSave(objetosSimulablesAllPages, path); - // Restore original properties + // Paso 4: Restaurar estado para seguir trabajando foreach (var obj in _mainViewModel.ObjetosSimulables) + { obj.RestaurarDatosNoSerializables(); + + // Para objetos globales, restaura los datos locales + if (obj.Enable_On_All_Pages && obj.Enable_Local_Data) + { + obj.RestaurarDatosLocales(); + } + } } } @@ -75,10 +108,47 @@ namespace CtrEditor.Serialization if (selectedImage != null) { var settings = GetJsonSerializerSettings(); - LoadCurrentPageState(selectedImage, settings); + + // Paso 1: Cargar objetos globales LoadAllPagesState(settings); - // Create UserControls for all loaded objects + // Paso 2: Cargar datos de la página actual + var simulationData = LoadCurrentPageState(selectedImage, settings); + + // Paso 3: Crear snapshots de los objetos globales + // Nota: No filtramos por Enable_Local_Data aquí ya que se establecerá después + foreach (var obj in _mainViewModel.ObjetosSimulables) + { + if (obj.Enable_On_All_Pages) + { + obj.CrearSnapshotGlobal(); + } + } + + // Paso 4: Aplicar datos locales a objetos globales + if (simulationData?.DatosLocalesObjetos != null) + { + // Primero, desactivar Enable_Local_Data en todos los objetos globales + foreach (var obj in _mainViewModel.ObjetosSimulables.Where(o => o.Enable_On_All_Pages)) + { + obj.Enable_Local_Data = false; + } + + // Luego aplicar los datos locales, esto activará Enable_Local_Data automáticamente + foreach (var datosLocales in simulationData.DatosLocalesObjetos) + { + var objetoGlobal = _mainViewModel.ObjetosSimulables.FirstOrDefault( + o => o.Enable_On_All_Pages && + o.Id.Value == datosLocales.Id_GlobalObject.Value); + + if (objetoGlobal != null) + { + objetoGlobal.AplicarDatosLocales(datosLocales); + } + } + } + + // Paso 5: Crear controles visuales foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables) { if (objetoSimulable != null) @@ -95,17 +165,21 @@ namespace CtrEditor.Serialization } } - private void LoadCurrentPageState(string selectedImage, JsonSerializerSettings settings) + private SimulationData LoadCurrentPageState(string selectedImage, JsonSerializerSettings settings) { + SimulationData simulationData = null; string jsonPath = _datosDeTrabajo.ObtenerPathImagenConExtension(selectedImage, ".json"); if (File.Exists(jsonPath)) { string jsonString = File.ReadAllText(jsonPath); - var simulationData = JsonConvert.DeserializeObject(jsonString, settings); + simulationData = JsonConvert.DeserializeObject(jsonString, settings); if (simulationData != null) { if (simulationData.ObjetosSimulables is not null) - _mainViewModel.ObjetosSimulables = simulationData.ObjetosSimulables; + { + foreach (var obj in simulationData.ObjetosSimulables) + _mainViewModel.ObjetosSimulables.Add(obj); + } if (simulationData.PLC_ConnectionData is not null) _mainViewModel.PLCViewModel = simulationData.PLC_ConnectionData; @@ -115,6 +189,7 @@ namespace CtrEditor.Serialization PixelToMeter.Instance.calc = simulationData.UnitConverter; } } + return simulationData; } private void LoadAllPagesState(JsonSerializerSettings settings) @@ -158,5 +233,4 @@ namespace CtrEditor.Serialization }; } } -} - +} \ No newline at end of file