WPF: come rendere il canvas auto-ridimensionato?

Vorrei che il mio Canvas si ridimensionasse automaticamente alla dimensione dei suoi elementi, in modo che le ScrollViewer scorrimento di ScrollViewer abbiano l’intervallo corretto. Questo può essere fatto in XAML?

       

Nel codice sopra la canvas ha sempre la dimensione 0, anche se non taglia i suoi figli.

No questo non è ansible (vedere il frammento di MSDN in basso). Tuttavia, se si desidera avere barre di scorrimento e ridimensionamento automatico, si consiglia di utilizzare invece una griglia e utilizzare la proprietà Margine per posizionare i propri elementi su questa griglia. La griglia indicherà a ScrollViewer quanto grande deve essere, e si otterrà il scrollbars .. Canvas dice sempre a ScrollViewer che non ha bisogno di alcuna dimensione .. 🙂

Grid ti fa apprezzare entrambi i mondi: finché metti tutti gli elementi in una singola cella, ottieni entrambi: Posizionamento arbitrario e ridimensionamento automatico. In generale è bene ricordare che la maggior parte dei controlli del pannello (DockPanel, StackPanel, ecc.) Può essere implementata tramite un controllo Grid.

Da MSDN :

Canvas è l’unico elemento del pannello che non ha caratteristiche di layout intrinseche. Una canvas ha le proprietà di altezza e larghezza predefinite pari a zero, a meno che non sia figlio di un elemento che dimensiona automaticamente gli elementi figlio. Gli elementi figlio di una canvas non vengono mai ridimensionati, vengono semplicemente posizionati alle coordinate designate. Ciò fornisce flessibilità per situazioni in cui i vincoli di dimensionamento inerente o l’allineamento non sono necessari o richiesti. Per i casi in cui desideri ridimensionare e allineare automaticamente i contenuti secondari, solitamente è preferibile utilizzare un elemento Grid.

Spero che questo ti aiuti

Sto solo copiando la risposta di illef qui, ma in risposta a PilotBob, devi solo definire un object di canvas come questo

 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 quindi usa CanvasAutoSize nel tuo XAML.

   

Preferisco questa soluzione a quella presentata sopra che utilizza la griglia mentre funziona attraverso le proprietà associate e richiede semplicemente l’impostazione di meno proprietà sugli elementi.

Penso che puoi ridimensionare la Canvas sostituendo i metodi MeasureOverride o ArrangeOverride .

Questo lavoro non è difficile.

Puoi vedere questo post. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Spero che questo ti aiuta.

Grazie.

Vedo che hai una soluzione praticabile, ma ho pensato di condividerlo.

   ...Content...   

La tecnica sopra consentirà di annidare una griglia all’interno di una canvas e di ridimensionare dynamicmente. Un ulteriore uso del legame di dimensioni consente di mescolare materiale dinamico con materiale statico, eseguire stratificazione, ecc. Ci sono troppe possibilità di menzionare, alcune più difficili di altre. Ad esempio, utilizzo l’approccio per simulare l’animazione dei contenuti spostandoli da una posizione di griglia a un’altra – effettuando il posizionamento effettivo nell’evento di completamento dell’animazione. In bocca al lupo.

In sostanza richiede una completa riscrittura di Canvas. Le soluzioni proposte in precedenza che annullano MeasureOverride non riescono perché le proprietà Canvas.Left / .Top & c predefinite invalidano l’Arrangment, ma ANCHE devono invalidare la misura. (Si ottiene la misura giusta la prima volta, ma la dimensione non cambia se si spostano elementi dopo il layout iniziale).

La soluzione Grid è più o meno ragionevole, ma vincolante per i Margini al fine di ottenere lo spostamento xy può causare il caos su altri codici (in particolare in MVVM). Ho faticato con la soluzione Grid view per un po ‘, ma le complicazioni con le interazioni View / ViewModel e il comportamento di scorrimento mi hanno finalmente portato a questo. Che è semplice e al punto, e funziona solo.

Non è così complicato implementare nuovamente ArrangeOverride e MeasureOverride. E tu sei destinato a scrivere almeno altrettanti codici che trattano della stupidità Grid / Margin. Quindi eccoti.

Ecco una soluzione più completa. il comportamento del margine diverso da zero non è stato verificato. Se hai bisogno di qualcosa di diverso da Left e Top, allora questo fornisce un punto di partenza, almeno.

AVVISO: è necessario utilizzare AutoResizeCanvas.Left e AutoResizeCanvas.Top proprietà associate anziché Canvas.Left e Canvas.Top. Le proprietà restanti della canvas non sono state implementate.

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

Associare l’altezza / larghezza alla dimensione effettiva del controllo all’interno della canvas ha funzionato per me:

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

Nella funzione, sto cercando di trovare la posizione X massima e la posizione Y, dove possono risiedere i controlli nell'area di disegno.

Utilizzare la funzione solo nell'evento Loaded o successivo e non nel costruttore. La finestra deve essere misurata prima del caricamento ..

Come miglioramento della risposta di @ MikeKulls, ecco una versione che non genera un’eccezione quando non ci sono elementi dell’interfaccia utente nell’area di disegno o quando ci sono elementi dell’interfaccia utente senza le proprietà 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() .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); } } 

Ho anche riscontrato questo problema, il mio problema era che la griglia non si ridimensionava automaticamente quando la canvas si ridimensionava grazie alla funzione di override di MeasureOverride.

il mio problema: WPF MeasureOverride loop

Sono stato in grado di ottenere il risultato che stai cercando semplicemente aggiungendo un nuovo evento modificato di dimensioni al controllo che conteneva i dati che stavano causando la crescita del canvas. Dopo che la canvas raggiunge l’estensione del visualizzatore di scorrimento, le barre di scorrimento verranno visualizzate. Ho appena assegnato la seguente espressione lambda all’evento di dimensione modificata del controllo:

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