.net wpf resize wpf-controls scrollviewer

.net - WPF: ¿Cómo hacer que el lienzo cambie automáticamente de tamaño?



resize wpf-controls (11)

Como una mejora a la respuesta de @ MikeKulls, aquí hay una versión que no lanza una excepción cuando no hay elementos de UI en el lienzo o cuando hay elementos de UI sin propiedades Canvas.Top o Canvas.Left:

public class AutoResizedCanvas : Canvas { protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint) { base.MeasureOverride(constraint); double width = base .InternalChildren .OfType<UIElement>() .Where(i => i.GetValue(Canvas.LeftProperty) != null) .Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty)); if (Double.IsNaN(width)) { width = 0; } double height = base .InternalChildren .OfType<UIElement>() .Where(i => i.GetValue(Canvas.TopProperty) != null) .Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty)); if (Double.IsNaN(height)) { height = 0; } return new Size(width, height); } }

Me gustaría que mi Canvas cambie automáticamente el tamaño del tamaño de sus elementos, de modo que las barras de desplazamiento ScrollViewer tengan el rango correcto. ¿Se puede hacer esto en XAML?

<ScrollViewer HorizontalScrollBarVisibility="Auto" x:Name="_scrollViewer"> <Grid x:Name ="_canvasGrid" Background="Yellow"> <Canvas x:Name="_canvas" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Green"></Canvas> <Line IsHitTestVisible="False" .../> </Grid> </ScrollViewer>

En el código anterior, el lienzo siempre tiene el tamaño 0, aunque no recorta sus elementos secundarios.



Esencialmente requiere una reescritura completa de Canvas. Las soluciones propuestas anteriores que anulan MeasureOverride fallan porque las propiedades predeterminadas Canvas.Left / .Top & c invalidan el Arrangment, pero TAMBIÉN necesitan invalidar la medida. (Obtiene el tamaño correcto la primera vez, pero el tamaño no cambia si mueve elementos después del diseño inicial).

La solución Grid es más o menos razonable, pero vinculante para los Márgenes con el fin de obtener un desplazamiento xy puede causar estragos en otro código (particalar en MVVM). Luché con la solución Grid view por un tiempo, pero las complicaciones con las interacciones View / ViewModel y el comportamiento de desplazamiento finalmente me llevaron a esto. Lo cual es simple y al grano, y solo funciona.

No es TAN complicado reintroducir ArrangeOverride y MeasureOverride. Y está obligado a escribir al menos la cantidad de código en cualquier otro lugar que tenga que ver con la estupidez de Grid / Margin. Así que ahí estás.

Aquí hay una solución más completa. el comportamiento de Margen no nulo no se ha probado. Si necesita algo más que el izquierdo y el superior, esto proporciona un punto de partida, al menos.

ADVERTENCIA: debe usar las propiedades adjuntas AutoResizeCanvas.Left y AutoResizeCanvas.Top en lugar de Canvas.Left y Canvas.Top. Las propiedades restantes del lienzo no se han implementado.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Mu.Controls { public class AutoResizeCanvas : Panel { public static double GetLeft(DependencyObject obj) { return (double)obj.GetValue(LeftProperty); } public static void SetLeft(DependencyObject obj, double value) { obj.SetValue(LeftProperty, value); } public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(AutoResizeCanvas), new FrameworkPropertyMetadata(0.0, OnLayoutParameterChanged)); private static void OnLayoutParameterChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { // invalidate the measure of the enclosing AutoResizeCanvas. while (d != null) { AutoResizeCanvas canvas = d as AutoResizeCanvas; if (canvas != null) { canvas.InvalidateMeasure(); return; } d = VisualTreeHelper.GetParent(d); } } public static double GetTop(DependencyObject obj) { return (double)obj.GetValue(TopProperty); } public static void SetTop(DependencyObject obj, double value) { obj.SetValue(TopProperty, value); } public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(AutoResizeCanvas), new FrameworkPropertyMetadata(0.0, OnLayoutParameterChanged)); protected override Size MeasureOverride(Size constraint) { Size availableSize = new Size(double.MaxValue, double.MaxValue); double requestedWidth = MinimumWidth; double requestedHeight = MinimumHeight; foreach (var child in base.InternalChildren) { FrameworkElement el = child as FrameworkElement; if (el != null) { el.Measure(availableSize); Rect bounds, margin; GetRequestedBounds(el,out bounds, out margin); requestedWidth = Math.Max(requestedWidth, margin.Right); requestedHeight = Math.Max(requestedHeight, margin.Bottom); } } return new Size(requestedWidth, requestedHeight); } private void GetRequestedBounds( FrameworkElement el, out Rect bounds, out Rect marginBounds ) { double left = 0, top = 0; Thickness margin = new Thickness(); DependencyObject content = el; if (el is ContentPresenter) { content = VisualTreeHelper.GetChild(el, 0); } if (content != null) { left = AutoResizeCanvas.GetLeft(content); top = AutoResizeCanvas.GetTop(content); if (content is FrameworkElement) { margin = ((FrameworkElement)content).Margin; } } if (double.IsNaN(left)) left = 0; if (double.IsNaN(top)) top = 0; Size size = el.DesiredSize; bounds = new Rect(left + margin.Left, top + margin.Top, size.Width, size.Height); marginBounds = new Rect(left, top, size.Width + margin.Left + margin.Right, size.Height + margin.Top + margin.Bottom); } protected override Size ArrangeOverride(Size arrangeSize) { Size availableSize = new Size(double.MaxValue, double.MaxValue); double requestedWidth = MinimumWidth; double requestedHeight = MinimumHeight; foreach (var child in base.InternalChildren) { FrameworkElement el = child as FrameworkElement; if (el != null) { Rect bounds, marginBounds; GetRequestedBounds(el, out bounds, out marginBounds); requestedWidth = Math.Max(marginBounds.Right, requestedWidth); requestedHeight = Math.Max(marginBounds.Bottom, requestedHeight); el.Arrange(bounds); } } return new Size(requestedWidth, requestedHeight); } public double MinimumWidth { get { return (double)GetValue(MinimumWidthProperty); } set { SetValue(MinimumWidthProperty, value); } } public static readonly DependencyProperty MinimumWidthProperty = DependencyProperty.Register("MinimumWidth", typeof(double), typeof(AutoResizeCanvas), new FrameworkPropertyMetadata(300.0,FrameworkPropertyMetadataOptions.AffectsMeasure)); public double MinimumHeight { get { return (double)GetValue(MinimumHeightProperty); } set { SetValue(MinimumHeightProperty, value); } } public static readonly DependencyProperty MinimumHeightProperty = DependencyProperty.Register("MinimumHeight", typeof(double), typeof(AutoResizeCanvas), new FrameworkPropertyMetadata(200.0,FrameworkPropertyMetadataOptions.AffectsMeasure)); } }


La vinculación de la altura / ancho con el tamaño real del control dentro del lienzo funcionó para mí:

<ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible"> <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}" Width="{Binding ElementName=myListBox, Path=ActualWidth}"> <ListBox x:Name="myListBox" /> </Canvas> </ScrollViewer>


No, esto no es posible (vea el fragmento de MSDN a continuación). Sin embargo, si desea tener barras de desplazamiento y cambio de tamaño automático, considere usar una Cuadrícula en su lugar, y use la propiedad Margen para colocar sus elementos en esta Cuadrícula. La cuadrícula le dirá al Visor de desplazamiento qué tan grande quiere ser, y obtendrá la barras de desplazamiento ... Canvas siempre le dice al ScrollViewer que no necesita ningún tamaño ... :)

Grid te permite disfrutar de ambos mundos: siempre que pongas todos los elementos en una sola celda, obtienes ambos: posicionamiento arbitrario y tamaño automático. En general, es bueno recordar que la mayoría de los controles de panel (DockPanel, StackPanel, etc.) se pueden implementar a través de un control de cuadrícula.

Desde MSDN :

Canvas es el único elemento de panel que no tiene características de diseño inherentes. Un lienzo tiene propiedades de altura y ancho predeterminadas de cero, a menos que sea el elemento secundario de un elemento que ajuste automáticamente sus elementos secundarios. Los elementos secundarios de un lienzo nunca se redimensionan, solo se colocan en sus coordenadas designadas. Esto proporciona flexibilidad para situaciones en las que las restricciones de tamaño inherentes o la alineación no son necesarias o deseadas. Para los casos en los que desea que el contenido secundario se redimensione y alinee automáticamente, generalmente es mejor usar un elemento Grid.

Espero que esto ayude


Pude lograr el resultado que busca simplemente agregando un nuevo evento de cambio de tamaño al control que contenía los datos que causaban que el lienzo creciera. Después de que el lienzo alcance la extensión del visor de desplazamiento, las barras de desplazamiento aparecerán. Acabo de asignar la siguiente expresión lambda al evento de cambio de tamaño del control:

text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height; DrawingCanvas.Width = e.NewSize.Width; };


Solo estoy copiando la respuesta de illef aquí, pero en respuesta a PilotBob, simplemente defines un objeto canvas como este

public class CanvasAutoSize : Canvas { protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint) { base.MeasureOverride(constraint); double width = base .InternalChildren .OfType<UIElement>() .Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty)); double height = base .InternalChildren .OfType<UIElement>() .Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty)); return new Size(width, height); } }

y luego use CanvasAutoSize en su XAML.

<local:CanvasAutoSize VerticalAlignment="Top" HorizontalAlignment="Left"></local:CanvasAutoSize>

Prefiero esta solución a la presentada arriba que usa la grilla ya que funciona a través de propiedades adjuntas y solo requiere establecer menos propiedades en los elementos.


También me he encontrado con este problema, mi problema era que la cuadrícula no cambiaba de tamaño automáticamente cuando el Lienzo se redimensionaba gracias a la función invalidada MeasureOverride.

mi problema: WPF MeasureOverride loop


Veo que tienes una solución viable, pero pensé que compartiría.

<Canvas x:Name="topCanvas"> <Grid x:Name="topGrid" Width="{Binding ElementName=topCanvas, Path=ActualWidth}" Height="{Binding ElementName=topCanvas, Path=ActualHeight}"> ...Content... </Grid> </Canvas>

La técnica anterior le permitirá anidar una cuadrícula dentro de un lienzo y tener un cambio de tamaño dinámico. El uso adicional de la unión de dimensión permite mezclar material dinámico con material estático, realizar estratificación, etc. Hay demasiadas posibilidades para mencionar, algunas más duras que otras. Por ejemplo, uso el enfoque para simular el contenido de animación moviéndose de una ubicación de cuadrícula a otra, haciendo la ubicación real en el evento de finalización de la animación. Buena suerte.


<viewbox> <canvas> <uielements /> </canvas> </viewbox>


void MainWindow_Loaded(object sender, RoutedEventArgs e) { autoSizeCanvas(canvas1); } void autoSizeCanvas(Canvas canv) { int height = canv.Height; int width = canv.Width; foreach (UIElement ctrl in canv.Children) { bool nullTop = ctrl.GetValue(Canvas.TopProperty) == null || Double.IsNaN(Convert.ToDouble(ctrl.GetValue(Canvas.TopProperty))), nullLeft = ctrl.GetValue(Canvas.LeftProperty) == null || Double.IsNaN(Convert.ToDouble(ctrl.GetValue(Canvas.LeftProperty))); int curControlMaxY = (nullTop ? 0 : Convert.ToInt32(ctrl.GetValue(Canvas.TopProperty))) + Convert.ToInt32(ctrl.GetValue(Canvas.ActualHeightProperty) ), curControlMaxX = (nullLeft ? 0 : Convert.ToInt32(ctrl.GetValue(Canvas.LeftProperty))) + Convert.ToInt32(ctrl.GetValue(Canvas.ActualWidthProperty) ); height = height < curControlMaxY ? curControlMaxY : height; width = width < curControlMaxX ? curControlMaxX : width; } canv.Height = height; canv.Width = width; }

En la función, estoy tratando de encontrar la posición X máxima y la posición Y, donde pueden residir los controles en el lienzo.

Use la función solo en el evento Loaded o posterior y no en el constructor. La ventana debe medirse antes de cargar.