From b48dbeb76ee8cad0c53d73cd1cc83c5b6e140e61 Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 18 Jun 2025 19:54:51 +0200 Subject: [PATCH] =?UTF-8?q?Se=20a=C3=B1adi=C3=B3=20un=20nuevo=20m=C3=A9tod?= =?UTF-8?q?o=20para=20configurar=20la=20escala=20desde=20el=20men=C3=BA=20?= =?UTF-8?q?contextual=20en=20MainViewModel,=20permitiendo=20a=20los=20usua?= =?UTF-8?q?rios=20ajustar=20la=20escala=20de=20simulaci=C3=B3n.=20Se=20imp?= =?UTF-8?q?lement=C3=B3=20la=20l=C3=B3gica=20para=20detener=20simulaciones?= =?UTF-8?q?,=20actualizar=20la=20escala=20en=20el=20convertidor=20de=20uni?= =?UTF-8?q?dades=20y=20forzar=20el=20redibujo=20del=20canvas.=20Adem=C3=A1?= =?UTF-8?q?s,=20se=20agreg=C3=B3=20una=20opci=C3=B3n=20en=20el=20men=C3=BA?= =?UTF-8?q?=20contextual=20de=20MainWindow=20para=20acceder=20a=20esta=20f?= =?UTF-8?q?uncionalidad.=20Se=20mejor=C3=B3=20la=20gesti=C3=B3n=20de=20bin?= =?UTF-8?q?dings=20de=20posici=C3=B3n=20y=20tama=C3=B1o=20en=20osBase=20pa?= =?UTF-8?q?ra=20asegurar=20actualizaciones=20adecuadas=20tras=20cambios=20?= =?UTF-8?q?de=20escala.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Converters/RegionalFloatConverter.cs | 46 ++++++++++++ DataStates/StateManager.cs | 3 +- MainViewModel.cs | 56 +++++++++++++++ MainWindow.xaml.cs | 51 +++++++------ ObjetosSim/osBase.cs | 17 +++++ PopUps/ScaleConfigWindow.xaml | 104 +++++++++++++++++++++++++++ PopUps/ScaleConfigWindow.xaml.cs | 92 ++++++++++++++++++++++++ Serialization/StateSerializer.cs | 4 +- XAMLhelpers.cs | 11 ++- 9 files changed, 357 insertions(+), 27 deletions(-) create mode 100644 Converters/RegionalFloatConverter.cs create mode 100644 PopUps/ScaleConfigWindow.xaml create mode 100644 PopUps/ScaleConfigWindow.xaml.cs diff --git a/Converters/RegionalFloatConverter.cs b/Converters/RegionalFloatConverter.cs new file mode 100644 index 0000000..f790e44 --- /dev/null +++ b/Converters/RegionalFloatConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CtrEditor.Converters +{ + public class RegionalFloatConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is float floatValue) + { + // Usar la cultura actual para mostrar el número con el separador decimal correcto + return floatValue.ToString("N4", CultureInfo.CurrentCulture); + } + return value?.ToString() ?? string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string stringValue) + { + // Intentar parsing con la cultura actual primero + if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.CurrentCulture, out float result)) + { + return result; + } + + // Si falla, intentar con cultura invariante (punto como separador) + if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) + { + return result; + } + + // Si ambos fallan, intentar reemplazar punto por coma o viceversa + string adjustedString = stringValue.Replace(',', '.').Replace(".", CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); + if (float.TryParse(adjustedString, NumberStyles.Float, CultureInfo.CurrentCulture, out result)) + { + return result; + } + } + + return 0.0f; // Valor por defecto si no se puede parsear + } + } +} \ No newline at end of file diff --git a/DataStates/StateManager.cs b/DataStates/StateManager.cs index 7387186..372686d 100644 --- a/DataStates/StateManager.cs +++ b/DataStates/StateManager.cs @@ -118,8 +118,7 @@ namespace CtrEditor _mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel(); if (_globalState.UnitConverter != null) PixelToMeter.Instance.calc = _globalState.UnitConverter; - else - PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto + // No crear un nuevo UnitConverter si no hay uno guardado, mantener el actual // Restaurar objetos globales foreach (var obj in _globalState.SharedObjects) diff --git a/MainViewModel.cs b/MainViewModel.cs index d8dddf5..b713f44 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -1236,6 +1236,62 @@ namespace CtrEditor OnPropertyChanged(nameof(SelectedItemOsList)); } + // Método para configurar la escala desde el menú contextual + public void ConfigureScale() + { + var currentScale = PixelToMeter.Instance.calc.Scale; + var scaleWindow = new PopUps.ScaleConfigWindow(currentScale); + scaleWindow.Owner = MainWindow; + + if (scaleWindow.ShowDialog() == true) + { + var newScale = scaleWindow.NewScale; + + // Detener simulaciones antes de cambiar la escala + StopSimulation(); + StopFluidSimulation(); + DisconnectPLC(); + + // Actualizar la escala en el UnitConverter + PixelToMeter.Instance.calc.SetScale(newScale); + + // Forzar redibujo completo del canvas y todos los objetos + ForceCanvasRedraw(); + + // Marcar como cambios no guardados ya que esto afecta la configuración de la imagen + HasUnsavedChanges = true; + + // Limpiar historial de undo después de cambiar la escala + MainWindow?.ClearUndoHistory(); + } + } + + // Método para forzar el redibujo completo del canvas + private void ForceCanvasRedraw() + { + // Forzar actualización de todos los objetos simulables mediante PropertyChanged + foreach (var obj in ObjetosSimulables) + { + // Forzar la notificación de PropertyChanged para las propiedades de posición y tamaño + // Esto hará que los bindings y converters se recalculen automáticamente + obj.ForceUpdatePositionBindings(); + } + + // Forzar actualización del layout del canvas principal + MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual(); + MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout(); + + // Actualizar selecciones visuales si existen + _objectManager?.UpdateSelectionVisuals(); + + // Forzar actualización global del layout con prioridad de renderizado + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => + { + MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual(); + MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout(); + })); + } + // Diccionario para manejar ventanas de biblioteca múltiples private Dictionary _libraryWindows = new(); diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 9e190c6..7a8e574 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -328,7 +328,7 @@ namespace CtrEditor { // Siempre trabajar con selección única para las propiedades CargarPropiedadesosDatos(selectedObject); - + _objectManager.SelectObject(selectedObject); } else if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0) @@ -414,7 +414,7 @@ namespace CtrEditor { // Capturar estado antes de mover _objectManager.CaptureUndoState(); - + // Mover todos los objetos primero foreach (var obj in _objectManager.SelectedObjects) { @@ -472,7 +472,7 @@ namespace CtrEditor if (DataContext is MainViewModel viewModel) { viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources); - + // If no object is selected, make sure to clear the properties panel if (selectedObject == null) { @@ -712,12 +712,19 @@ namespace CtrEditor contextMenu.Items.Add(lockSubmenu); contextMenu.Items.Add(new Separator()); } - + + // Agregar opción de configurar escala + var scaleConfigItem = new MenuItem { Header = "Configurar Escala..." }; + scaleConfigItem.Click += (s, e) => viewModel.ConfigureScale(); + contextMenu.Items.Add(scaleConfigItem); + + contextMenu.Items.Add(new Separator()); + // Agregar información del sistema de undo var undoHistoryCount = _objectManager.GetUndoHistoryCount(); var canUndo = _objectManager.CanUndo(); - var undoInfoItem = new MenuItem - { + var undoInfoItem = new MenuItem + { Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})", IsEnabled = false, FontStyle = FontStyles.Italic @@ -820,7 +827,7 @@ namespace CtrEditor { // Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización var objectsCopy = new List(); - + foreach (var originalObj in _objectManager.SelectedObjects) { try @@ -854,7 +861,7 @@ namespace CtrEditor string jsonString = JsonConvert.SerializeObject(objectsCopy, settings); Clipboard.SetText(jsonString); - + Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles"); } } @@ -899,7 +906,7 @@ namespace CtrEditor try { string jsonString = Clipboard.GetText(); - + // Validación básica del JSON if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{"))) { @@ -984,11 +991,11 @@ namespace CtrEditor { // Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto) obj.Id.ObtenerNuevaID(); - + string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$") ? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}") : obj.Nombre + "_" + obj.Id.Value; - + obj.Nombre = nombre; } else @@ -1005,7 +1012,7 @@ namespace CtrEditor viewModel.ObjetosSimulables.Add(obj); viewModel.CrearUserControlDesdeObjetoSimulable(obj); viewModel.HasUnsavedChanges = true; - + newlyPastedObjects.Add(obj); } catch (Exception ex) @@ -1017,10 +1024,10 @@ namespace CtrEditor // Usar la misma lógica de selección que DuplicarUserControl // Limpiar selección antes de seleccionar los nuevos objetos _objectManager.ClearSelection(); - + // Forzar actualización completa del layout ImagenEnTrabajoCanvas.UpdateLayout(); - + // Usar dispatcher con la misma prioridad que DuplicarUserControl Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { @@ -1031,7 +1038,7 @@ namespace CtrEditor { double left = Canvas.GetLeft(newObj.VisualRepresentation); double top = Canvas.GetTop(newObj.VisualRepresentation); - + // Solo añadir a selección si el objeto tiene coordenadas válidas if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top)) { @@ -1039,10 +1046,10 @@ namespace CtrEditor } } } - + // Forzar otra actualización del layout antes de actualizar visuales ImagenEnTrabajoCanvas.UpdateLayout(); - + // Actualizar SelectedItemOsList si hay objetos pegados if (newlyPastedObjects.Count > 0) { @@ -1051,13 +1058,13 @@ namespace CtrEditor vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault(); } } - + // Actualizar visuales de selección _objectManager.UpdateSelectionVisuals(); - + Console.WriteLine($"Pegados y seleccionados {newlyPastedObjects.Count} objeto(s)"); })); - + Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles"); } } @@ -1075,9 +1082,9 @@ namespace CtrEditor try { string jsonString = Clipboard.GetText(); - + // Validación básica del JSON - if (string.IsNullOrWhiteSpace(jsonString) || + if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{"))) { return false; diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index b2c4f05..0a00369 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -1195,6 +1195,23 @@ namespace CtrEditor.ObjetosSim CanvasSetTopinMeter(Top); } + /// + /// Fuerza la actualización de todos los bindings de posición y tamaño + /// disparando PropertyChanged para Left, Top, Ancho, Alto + /// + public void ForceUpdatePositionBindings() + { + // Para Left y Top, actualizar directamente el Canvas ya que tienen lógica especial en OnChanged + OnPropertyChanged(nameof(Left)); + OnPropertyChanged(nameof(Top)); + // CanvasSetLeftinMeter(Left); + // CanvasSetTopinMeter(Top); + + // Para Ancho y Alto, disparar PropertyChanged para que los bindings se actualicen + OnPropertyChanged(nameof(Ancho)); + OnPropertyChanged(nameof(Alto)); + } + public bool LeerBitTag(string Tag) diff --git a/PopUps/ScaleConfigWindow.xaml b/PopUps/ScaleConfigWindow.xaml new file mode 100644 index 0000000..b00a33f --- /dev/null +++ b/PopUps/ScaleConfigWindow.xaml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +