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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +