WPF: como fazer o redimensionamento automático da canvas?

Gostaria que meu Canvas fosse redimensionado automaticamente para o tamanho de seus itens, para que as barras de rolagem do ScrollViewer tenham o intervalo correto. Isso pode ser feito em XAML?

       

No código acima, a canvas sempre tem tamanho 0, apesar de não cortar seus filhos.

Não, isso não é possível (veja o trecho do MSDN abaixo). No entanto, se você quiser ter barras de rolagem e redimensionamento automático, considere o uso de uma Grade e use a propriedade Margem para posicionar seus itens nessa Grade. Grid dirá ao ScrollViewer o tamanho que ele deseja ser, e você obterá o barras de rolagem .. Canvas sempre diz o ScrollViewer ele não precisa de qualquer tamanho .. 🙂

A grade permite que você aproveite os dois mundos – Desde que você coloque todos os elementos em uma única célula, você terá os dois: Posicionamento arbitrário e dimensionamento automático. Em geral, é bom lembrar que a maioria dos controles do painel (DockPanel, StackPanel, etc) pode ser implementada por meio de um controle Grid.

Do MSDN :

Canvas é o único elemento de painel que não possui características de layout inerentes. Um Canvas tem propriedades padrão de Altura e Largura de zero, a menos que seja o filho de um elemento que dimensiona automaticamente seus elementos filhos. Elementos filho de uma canvas nunca são redimensionados, eles são apenas posicionados em suas coordenadas designadas. Isso proporciona flexibilidade para situações em que restrições ou alinhamento de dimensionamento inerente não são necessários ou desejados. Para casos em que você deseja que o conteúdo filho seja automaticamente redimensionado e alinhado, geralmente é melhor usar um elemento de Grade.

Espero que isto ajude

Eu estou apenas copiando a resposta do illef aqui, mas em resposta ao PilotBob, você acabou de definir um object de 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() .Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty)); double height = base .InternalChildren .OfType() .Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty)); return new Size(width, height); } } 

e, em seguida, use CanvasAutoSize no seu XAML.

   

Eu prefiro esta solução para o apresentado acima que usa a grade como funciona através de propriedades anexadas e apenas requer a configuração de menos propriedades nos elementos.

Acho que você pode resize o Canvas substituindo os methods MeasureOverride ou ArrangeOverride .

Este trabalho não é difícil.

Você pode ver este post. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Espero que isso ajude você.

Obrigado.

Eu vejo que você tem uma solução viável, mas eu pensei em compartilhar.

   ...Content...   

A técnica acima permitirá que você aninhe uma grade dentro de uma canvas e tenha redimensionamento dynamic. O uso adicional de encadernação de dimensões torna possível misturar material dynamic com material estático, executar camadas, etc. Há muitas possibilidades para mencionar, algumas mais difíceis do que outras. Por exemplo, eu uso a abordagem para simular a animação de conteúdo movendo-se de um local de grade para outro – fazendo o posicionamento real no evento de conclusão da animação. Boa sorte.

Essencialmente, requer uma reescrita completa do Canvas. As soluções propostas anteriores que substituem MeasureOverride falham porque as propriedades padrão Canvas.Left / .Top & c invalidam Arrangment, mas TAMBÉM precisam invalidar a medida. (Você obtém o tamanho certo na primeira vez, mas o tamanho não muda se você mover elementos após o layout inicial).

A solução Grid é mais ou menos razoável, mas a vinculação às Margens para obter o deslocamento xy pode causar estragos em outro código (particalary no MVVM). Lutei com a solução de visualização de grade por um tempo, mas as complicações com as interações View / ViewModel e o comportamento de rolagem finalmente me levaram a isso. Que é simples e direto ao ponto, e apenas funciona.

Não é tão complicado reimplementar ArrangeOverride e MeasureOverride. E você está fadado a escrever pelo menos o mesmo código em qualquer outro lugar, lidando com estupidez de Grid / Margin. Então você está aí.

Aqui está uma solução mais completa. Comportamento de margem diferente de zero não foi testado. Se você precisar de algo diferente de Left e Top, isso fornece um ponto de partida, pelo menos.

AVISO: você deve usar as propriedades anexadas AutoResizeCanvas.Left e AutoResizeCanvas.Top em vez de Canvas.Left e Canvas.Top. As propriedades restantes da canvas não foram implementadas.

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

Vinculando a Altura / Largura ao tamanho real do controle dentro da canvas funcionou para mim:

       
 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; } 

Na function, estou tentando encontrar a posição X máxima e a posição Y, onde os controles na canvas podem residir.

Use a function apenas no evento Loaded ou posterior e não no construtor. A janela deve ser medida antes do carregamento.

Como uma melhoria para a resposta do @ MikeKulls, aqui está uma versão que não lança uma exceção quando não há elementos da interface do usuário na canvas ou quando há elementos da interface do usuário sem propriedades Canvas.Top ou Canvas.Left:

 public class AutoResizedCanvas : Canvas { protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint) { base.MeasureOverride(constraint); double width = base .InternalChildren .OfType() .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() .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); } } 

Eu também encontrei este problema, meu problema era que a grade não era redimensionada automaticamente quando o Canvas se redimensionava graças à function MeasureOverride sobreposta.

meu problema: loop WPF MeasureOverride

Consegui alcançar o resultado que você está procurando simplesmente adicionando um novo evento de tamanho alterado ao controle que continha os dados que estavam causando o aumento da canvas. Depois que a canvas atingir a extensão do visualizador de rolagem, isso fará com que as barras de rolagem apareçam. Acabei de atribuir a seguinte expressão lambda ao evento de tamanho alterado do controle:

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