2009-05-12 24 views
38

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?WPF: ¿Cómo hacer que el lienzo cambie el tamaño automáticamente?

<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.

Respuesta

45

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 indicará al desplazador de desplazamiento qué tan grande quiere ser, y lo hará obtener las 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 auto-tamaño. 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.

De MSDN:

lienzo es el único elemento de panel que no tiene características inherentes de diseño. 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.

Esperanza esto ayuda

+5

me cambiaron de lienzo a la cuadrícula y funcionó, después de algunos ajustes. Tuve que hacer dos cambios: (1) en todos los lugares donde solía establecer las propiedades adjuntas Canvas.Left y Canvas.Top, ahora configuré las propiedades regulares Margin.Left y Margin.Top (Margin.Right y Margin.Bottom se pueden a la izquierda en 0); (2) use la Alineación Horizontal = "Izquierda" y la Alineación Vertical = "Superior" en cada elemento en la Rejilla. El modo predeterminado "Estirar" puede hacer que los elementos terminen en el centro cuando los márgenes son 0. – Qwertie

+2

"Para los casos en los que desea que el contenido secundario se redimensione y alinee automáticamente, generalmente es mejor usar un elemento Grid". Pero la pregunta original es sobre el cambio de tamaño del lienzo, no los elementos secundarios.Creo que la solución proporcionada por illef a continuación responde mejor esta pregunta y evita establecer tantas propiedades en todos los elementos secundarios. Con la respuesta de Illef, simplemente configura las propiedades adjuntas Superior e Izquierda que creo que es una solución más ordenada. Una vez que haya definido el nuevo objeto Canvas, se trata de una solución reutilizable que puede usarse en cualquier otro lugar de su proyecto. – MikeKulls

+0

El problema que tengo con esto es que al renderizarlo no se superponen los controles y se desvían por todos lados, a menos que, por supuesto, estoy haciendo algo mal. Además, la solución illef es un error de cálculo por alguna razón. –

6

veo que tienes una solución viable, pero pensé que me gustaría compartir.

<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.

-3
<viewbox> 
    <canvas> 
     <uielements /> 
    </canvas> 
</viewbox> 
+4

Una pequeña propaganda en un párrafo habría sido agradable además de la respuesta. – slm

+1

esto no va a funcionar – Liero

9

creo que se puede cambiar el tamaño de Canvas anulando MeasureOverride o ArrangeOverride métodos.

Este trabajo no es difícil.

Puede ver esta publicación. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Espero que esto te ayude.

Gracias.

+0

Necesito hacer esto, pero tengo problemas con el código presentado. ¿Qué quiere decir con "definir nuevo lienzo"? ¿Te refieres a una clase que deriva de Canvas? Si es así, obtengo no contiene def para InternalChildren y no puedo anular el miembro heredado. – PilotBob

+0

Ver mi respuesta a continuación – MikeKulls

+0

+1 Gran idea sobre cómo lidiar con el requisito! Además, propondría ampliar su código con un double.IsNaN- compruebe los valores superiores e izquierdos y configúrelos a cero si son NaN. – HCL

32

Sólo estoy copiando la respuesta de illef aquí, sino en respuesta a PilotBob, que acaba de definir un objeto de tela como esto

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 utilice CanvasAutoSize en su XAML.

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

prefiero esta solución a la presentada anteriormente que utiliza la red, ya que funciona a través de propiedades adjuntas y sólo requiere el establecimiento de menos propiedades de los elementos.

+0

¿Cómo puedo obtener el error de tiempo de ejecución sobre 'ancho' y 'alto' en MeasureOverride diciendo que tanto 'ancho' como 'alto' son NaN – jondinham

+2

y descubrí por qué, todos los elementos de CanvasAutoSize deben tener Canvas.Left & Canvas.Top establecido – jondinham

+4

Acepto, esta es una solución mucho mejor que usar una Grid si está haciendo algo complejo en términos de diseño. Por ejemplo, estaba vinculando la altura de un control con otro y, por lo tanto, agregar un margen estaba causando algún tipo de recursión de diseño infinito. Sin embargo, como dice Paul Dinh, esta solución se arruina si no configura Canvas.Left y Canvas. Toque cada elemento, lo cual es molesto si tiene varios en (0, 0). Cambié el cuerpo del Max() lambda para asignar Canvas.Left/Top a un doble y verifico double.IsNaN(), y si es así, uso 0.0 en su lugar. Eso funciona genial –

2

Encuadernación la altura/anchura al tamaño real del control dentro del lienzo trabajó 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> 
+0

Supongo que funciona si solo necesita un solo hijo para controlar el tamaño del lienzo. ¿Pero por qué poner ListBox en Canvas en ScrollViewer en lugar de simplemente usar ListBox solo? – Qwertie

5

Esencialmente se requiere una reescritura completa del lienzo. Las soluciones propuestas anteriores que anulan MeasureOverride fallan porque las propiedades predeterminadas de 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 de cuadrícula es más o menos razonable, pero vinculante para los márgenes con el fin de obtener el desplazamiento x-y 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)); 



    } 


} 
+1

Saber cuándo renunciar a una solución hackish es invaluable! La implementación de un panel personalizado es la solución correcta en muchos casos. – mydogisbox

+0

Esta es la mejor respuesta – Tuco

0
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 los controles en el lienzo pueden residir.

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

0

También he encontrado 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

0

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

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); 
    } 
} 
Cuestiones relacionadas