Se añadió un nuevo método para configurar la escala desde el menú contextual en MainViewModel, permitiendo a los usuarios ajustar la escala de simulación. Se implementó la lógica para detener simulaciones, actualizar la escala en el convertidor de unidades y forzar el redibujo del canvas. Además, se agregó una opción en el menú contextual de MainWindow para acceder a esta funcionalidad. Se mejoró la gestión de bindings de posición y tamaño en osBase para asegurar actualizaciones adecuadas tras cambios de escala.
This commit is contained in:
parent
ca70f66ff1
commit
b48dbeb76e
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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<string, PopUps.LibraryWindow> _libraryWindows = new();
|
||||
|
||||
|
|
|
@ -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<osBase>();
|
||||
|
||||
|
||||
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;
|
||||
|
|
|
@ -1195,6 +1195,23 @@ namespace CtrEditor.ObjetosSim
|
|||
CanvasSetTopinMeter(Top);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuerza la actualización de todos los bindings de posición y tamaño
|
||||
/// disparando PropertyChanged para Left, Top, Ancho, Alto
|
||||
/// </summary>
|
||||
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)
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<Window x:Class="CtrEditor.PopUps.ScaleConfigWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:CtrEditor.Converters"
|
||||
Title="Configurar Escala"
|
||||
Height="280"
|
||||
Width="450"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize">
|
||||
<Window.Resources>
|
||||
<converters:RegionalFloatConverter x:Key="RegionalFloatConverter"/>
|
||||
</Window.Resources>
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Título -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="Configuración de Escala de Conversión"
|
||||
FontWeight="Bold"
|
||||
FontSize="14"
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
<!-- Escala actual -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="Escala actual: " VerticalAlignment="Center" Width="120"/>
|
||||
<TextBlock VerticalAlignment="Center" FontWeight="Bold">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} m/pixel">
|
||||
<Binding Path="CurrentScale" Converter="{StaticResource RegionalFloatConverter}"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Nueva escala -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="Nueva escala: " VerticalAlignment="Center" Width="120"/>
|
||||
<TextBox x:Name="ScaleTextBox"
|
||||
Text="{Binding NewScale, Converter={StaticResource RegionalFloatConverter}, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,5,0"/>
|
||||
<TextBlock Text="m/pixel" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Presets -->
|
||||
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0,0,0,15">
|
||||
<TextBlock Text="Presets comunes:" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<WrapPanel>
|
||||
<Button Content="1:1 (0.01)" Command="{Binding SetPresetCommand}" CommandParameter="0.01" Margin="0,0,5,5"/>
|
||||
<Button Content="1:10 (0.001)" Command="{Binding SetPresetCommand}" CommandParameter="0.001" Margin="0,0,5,5"/>
|
||||
<Button Content="1:100 (0.0001)" Command="{Binding SetPresetCommand}" CommandParameter="0.0001" Margin="0,0,5,5"/>
|
||||
<Button Content="1 px = 1 cm (0.01)" Command="{Binding SetPresetCommand}" CommandParameter="0.01" Margin="0,0,5,5"/>
|
||||
<Button Content="1 px = 1 mm (0.001)" Command="{Binding SetPresetCommand}" CommandParameter="0.001" Margin="0,0,5,5"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Información de ayuda -->
|
||||
<Border Grid.Row="5"
|
||||
Background="LightYellow"
|
||||
BorderBrush="Orange"
|
||||
BorderThickness="1"
|
||||
Padding="10"
|
||||
Margin="0,0,0,15">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Información:" FontWeight="Bold" Margin="0,0,0,5"/>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Text="La escala define cuántos metros representa cada píxel en el canvas."/>
|
||||
<LineBreak/>
|
||||
<Run Text="• Valores menores = objetos más pequeños en pantalla"/>
|
||||
<LineBreak/>
|
||||
<Run Text="• Valores mayores = objetos más grandes en pantalla"/>
|
||||
<LineBreak/>
|
||||
<Run Text="• Ejemplo: 0.01 significa que 1 píxel = 1 centímetro"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Botones -->
|
||||
<StackPanel Grid.Row="6"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="Aceptar"
|
||||
Command="{Binding AcceptCommand}"
|
||||
Width="80"
|
||||
Height="30"
|
||||
Margin="0,0,10,0"
|
||||
IsDefault="True"/>
|
||||
<Button Content="Cancelar"
|
||||
Command="{Binding CancelCommand}"
|
||||
Width="80"
|
||||
Height="30"
|
||||
IsCancel="True"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -0,0 +1,92 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace CtrEditor.PopUps
|
||||
{
|
||||
public partial class ScaleConfigWindow : Window
|
||||
{
|
||||
public ScaleConfigWindow(float currentScale)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new ScaleConfigViewModel(currentScale, this);
|
||||
}
|
||||
|
||||
public float NewScale => ((ScaleConfigViewModel)DataContext).NewScale;
|
||||
}
|
||||
|
||||
public partial class ScaleConfigViewModel : ObservableObject
|
||||
{
|
||||
private readonly ScaleConfigWindow _window;
|
||||
|
||||
[ObservableProperty]
|
||||
private float currentScale;
|
||||
|
||||
[ObservableProperty]
|
||||
private float newScale;
|
||||
|
||||
public ICommand SetPresetCommand { get; }
|
||||
public ICommand AcceptCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
|
||||
public ScaleConfigViewModel(float currentScale, ScaleConfigWindow window)
|
||||
{
|
||||
_window = window;
|
||||
CurrentScale = currentScale;
|
||||
NewScale = currentScale;
|
||||
|
||||
SetPresetCommand = new RelayCommand<object>(SetPreset);
|
||||
AcceptCommand = new RelayCommand(Accept, CanAccept);
|
||||
CancelCommand = new RelayCommand(Cancel);
|
||||
}
|
||||
|
||||
private void SetPreset(object parameter)
|
||||
{
|
||||
if (parameter != null && float.TryParse(parameter.ToString(),
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out float presetValue))
|
||||
{
|
||||
NewScale = presetValue;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanAccept()
|
||||
{
|
||||
return NewScale > 0; // Solo verificar que sea positivo
|
||||
}
|
||||
|
||||
private void Accept()
|
||||
{
|
||||
if (NewScale <= 0)
|
||||
{
|
||||
MessageBox.Show("La escala debe ser un valor positivo mayor que cero.",
|
||||
"Error de Validación",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debugging: mostrar mensaje para confirmar que se ejecuta
|
||||
// MessageBox.Show($"Accept ejecutado con escala: {NewScale}", "Debug", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
_window.DialogResult = true;
|
||||
// No necesitamos llamar Close() ya que establecer DialogResult automáticamente cierra la ventana
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
_window.DialogResult = false;
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
partial void OnNewScaleChanged(float value)
|
||||
{
|
||||
// Forzar la re-evaluación del comando Accept cuando el valor cambia
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -187,7 +187,9 @@ namespace CtrEditor.Serialization
|
|||
else
|
||||
_mainViewModel.PLCViewModel = new PLCViewModel();
|
||||
|
||||
PixelToMeter.Instance.calc = simulationData.UnitConverter;
|
||||
// Solo sobrescribir el UnitConverter si existe uno guardado
|
||||
if (simulationData.UnitConverter != null)
|
||||
PixelToMeter.Instance.calc = simulationData.UnitConverter;
|
||||
|
||||
// Cargar datos de imágenes
|
||||
if (simulationData.ImageDataDictionary != null)
|
||||
|
|
|
@ -314,7 +314,7 @@ namespace CtrEditor
|
|||
{
|
||||
if (values.Length == 2 && values[0] is float value1 && values[1] is float value2)
|
||||
{
|
||||
return (double) (value1 + value2);
|
||||
return (double)(value1 + value2);
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
@ -435,7 +435,14 @@ namespace CtrEditor
|
|||
{
|
||||
// Instancia privada estática, parte del patrón Singleton
|
||||
private static PixelToMeter? _instance;
|
||||
public UnitConverter calc = new UnitConverter(0.01f);
|
||||
public UnitConverter calc;
|
||||
|
||||
// Constructor privado para el patrón Singleton
|
||||
private PixelToMeter()
|
||||
{
|
||||
// Solo inicializar con valor por defecto si no se ha configurado antes
|
||||
calc = new UnitConverter(0.01f);
|
||||
}
|
||||
|
||||
// Propiedad pública estática para acceder a la instancia
|
||||
public static PixelToMeter Instance
|
||||
|
|
Loading…
Reference in New Issue