2009-05-12 18 views
38

Desidero che il mio Canvas ridimensioni automaticamente alla dimensione dei suoi elementi, in modo che le barre di scorrimento ScrollViewer abbiano l'intervallo corretto. Questo può essere fatto in XAML?WPF: come rendere il ridimensionamento automatico della tela?

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

Nel codice precedente la tela ha sempre la dimensione 0, sebbene non ritenga i suoi figli.

risposta

45

No, non è possibile (vedere lo snippet da MSDN in basso). Tuttavia, se si desidera avere barre di scorrimento e ridimensionamento automatico, considerare l'uso di una griglia e utilizzare la proprietà Margine per posizionare gli oggetti su questa griglia. La griglia dirà al ScrollViewer quanto grande vuole essere, e si ottieni le barre di scorrimento .. Canvas dice sempre a ScrollViewer che non ha bisogno di alcuna dimensione .. :)

Grid ti fa apprezzare entrambi i mondi - Finché stai mettendo 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:

tela è l'unico elemento di pannello che non ha caratteristiche di layout intrinseche. Una tela 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 tela 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 aiuti

+5

Sono passato da Canvas a Grid e ha funzionato, dopo alcuni ritocchi. Ho dovuto apportare due modifiche: (1) ovunque che ho usato per impostare le proprietà allegate Canvas.Left e Canvas.Top, ora ho impostato le proprietà regolari Margin.Left e Margin.Top (Margin.Right e Margin.Bottom possono essere lasciato a 0); (2) utilizzare HorizontalAlignment = "Left" e VerticalAlignment = "Top" su ogni elemento della griglia. La modalità "Stretch" predefinita può far sì che gli elementi finiscano al centro quando i margini sono 0. – Qwertie

+2

"Per i casi in cui desideri ridimensionare e allineare automaticamente i contenuti secondari, solitamente è preferibile utilizzare un elemento Grid." Ma la domanda iniziale riguarda il ridimensionamento della tela, non gli elementi figlio.Penso che la soluzione fornita da illef qui sotto risponda meglio a questa domanda ed eviti di impostare così tante proprietà su tutti gli elementi figli. Con la risposta di illef è sufficiente impostare le proprietà associate Top e Left che ritengo sia una soluzione più ordinata. Una volta definito il nuovo oggetto Canvas, si tratta di una soluzione riutilizzabile che può essere utilizzata altrove nel progetto. – MikeKulls

+0

Il problema che ho con questo è che quando si esegue il rendering non si sovrappongono i controlli e vengono smistati ovunque, a meno che, naturalmente, non stia facendo qualcosa di sbagliato. Anche la soluzione illef è male calcolata per qualche motivo. –

6

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

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

Questa tecnica consente di annidare una griglia all'interno di una tela e ridimensionare in modo dinamico. 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.

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

Un po 'di confusione in un paragrafo sarebbe stato bello in aggiunta alla risposta. – slm

+1

questo non funziona. – Liero

9

Penso che si possa ridimensionare Canvas sovrascrivendo MeasureOverride o ArrangeOverride metodi.

Questo lavoro non è difficile.

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

Spero che questo ti aiuti.

Grazie.

+0

Ho bisogno di farlo, ma ho problemi con il codice presentato. Cosa intendi esattamente per "definire una nuova tela". Intendi una classe che deriva da Canvas? Se è così, I get non contiene def per InternalChildren e non può sovrascrivere il membro ereditato. – PilotBob

+0

Vedere la mia risposta sotto – MikeKulls

+0

+1 Ottima idea su come affrontare il requisito! Inoltre, proporrei di estendere il codice con una doppia.IsNaN- controlla i valori superiore e sinistro e li imposta a zero se sono NaN. – HCL

32

sto semplicemente copiando la risposta di illef qui, ma in risposta alle PilotBob, basta definire un oggetto 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<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); 
    } 
} 

e quindi utilizzare CanvasAutoSize in XAML.

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

preferisco questa soluzione a quella presentata sopra che utilizza la rete che opera tramite proprietà associate e richiede solo modificando meno proprietà sugli elementi.

+0

come mai ottengo errore di runtime su 'width' e 'height' in MeasureOverride dicendo che sia 'width' che 'height' sono NaN – jondinham

+2

ho scoperto perché, tutti gli elementi in CanvasAutoSize devono avere Canvas.Left & Canvas.Top set – jondinham

+4

Concordo, questa è una soluzione molto migliore rispetto all'utilizzo di una griglia se si sta facendo qualcosa di complesso in termini di layout. Per esempio, stavo legando l'altezza di un controllo a un altro, e così l'aggiunta di un margine stava causando una sorta di ricorsione del layout infinita. Come dice Paul Dinh, però, questa soluzione si rovina se non imposti Canvas.Left e Canvas.Top su ogni elemento, il che è fastidioso se ne hai diversi a (0, 0). Ho cambiato il corpo del lambda Max() per assegnare Canvas.Left/Top a un double e controllare double.IsNaN(), e in questo caso usare 0.0 invece. Funziona alla grande. –

2

Binding l'altezza/larghezza alla dimensione effettiva del controllo all'interno della tela ha lavorato per me:

 <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

Immagino che funzioni se hai bisogno di un solo bambino per controllare le dimensioni della tela. Ma perché mettere ListBox su Canvas su ScrollViewer invece di usare solo ListBox da solo? – Qwertie

5

Essenzialmente si richiede una riscrittura completa di tela di canapa. Le soluzioni proposte in precedenza che annullano MeasureOverride non riescono perché le proprietà predefinite Canvas.Left/.Top & c 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 x-y può causare il caos su altro codice (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.

ATTENZIONE: è necessario utilizzare AutoResizeCanvas.Left e AutoResizeCanvas.Top proprietà collegate anziché Canvas.Left e Canvas.Top. Le proprietà restanti della tela 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)); 



    } 


} 
+1

Sapere quando rinunciare a una soluzione hackish è inestimabile! L'implementazione di un pannello personalizzato è la soluzione giusta in molti casi. – mydogisbox

+0

Questa è la migliore risposta – 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; 
} 

Nella funzione, sto cercando di trovare la posizione X massima e la posizione Y, in cui i controlli in tela possono risiedere.

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

0

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

il mio problema: WPF MeasureOverride loop

0

Come un miglioramento a @ risposta di MikeKulls, Ecco una versione che non un'eccezione quando non ci sono elementi dell'interfaccia utente nella tela o quando ci sono elementi dell'interfaccia utente senza Canvas.Top o Proprietà 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); 
    } 
} 
Problemi correlati