WPF: comment faire de canvas auto-redimensionner?

je voudrais que mon Canvas se redimensionne automatiquement à la taille de ses articles, de sorte que les barres de défilement ScrollViewer ont la gamme correcte. Cela peut-il être fait à 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>

dans le code ci-dessus la toile a toujours la taille 0, bien qu'elle ne clip ses enfants.

39
demandé sur Dave Clemmer 2009-05-13 03:09:00

11 réponses

Non cela n'est pas possible (voir extrait de MSDN ci-dessous). Cependant, si vous voulez avoir des barres de défilement et un redimensionnement automatique, envisagez d'utiliser une "grille à la place, et utilisez la propriété Margin pour positionner vos articles sur cette grille.. Grid dira au ScrollViewer à quel point il veut être grand, et vous aurez les scrollbars.. Canvas dira toujours au ScrollViewer qu'il n'a besoin d'aucune taille.. :)

Grid vous permet de profiter des deux mondes-aussi longtemps que vous êtes en mettant tous les éléments dans une seule cellule, vous obtenez à la fois: positionnement arbitraire et auto-dimensionnement. En général, il est bon de se rappeler que la plupart des contrôles de panneau (DockPanel, StackPanel, etc) peuvent être mis en œuvre via un contrôle de grille.

From MSDN :

la toile est le seul élément du panneau qui n'a pas de caractéristiques de disposition inhérentes. Une toile a par défaut des propriétés de hauteur et de largeur de zéro, à moins qu'elle ne soit l'enfant d'un élément qui dimensionne automatiquement ses éléments enfant. Les éléments enfants d'une toile ne sont jamais redimensionnés, ils sont simplement positionnés à leurs coordonnées désignées. Cela offre une certaine souplesse dans les situations où les contraintes inhérentes au calibrage ou à l'alignement ne sont pas nécessaires ou souhaitées. Pour les cas où vous souhaitez l'enfant contenu sera automatiquement redimensionnée et alignés, il est généralement préférable d'utiliser un élément de Grille.

Espérons que cette aide

45
répondu Arcturus 2011-02-10 16:40:19

Je ne fais que Copier la réponse d'illef ici mais en réponse à PilotBob, vous venez de définir un objet de toile comme ceci

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

et ensuite utiliser CanvasAutoSize dans votre XAML.

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

je préfère cette solution à celle présentée ci-dessus qui utilise la grille car elle fonctionne à travers les propriétés attachées et nécessite juste de définir moins de propriétés sur les éléments.

33
répondu MikeKulls 2011-08-19 00:38:43

je pense que vous pouvez redimensionner Canvas en remplaçant MeasureOverride ou ArrangeOverride méthodes.

Ce travail n'est pas difficile.

vous pouvez voir ce post. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

j'espère que cela vous aide.

Merci.

9
répondu illef 2013-09-06 16:46:36

je vois que vous avez une solution viable, mais j'ai pensé que je partagerais.

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

La technique ci-dessus vous permettra de nid d'une grille à l'intérieur d'une toile et d'avoir un redimensionnement dynamique. L'utilisation ultérieure de la liaison dimensionnelle permet de mélanger un matériau dynamique avec un matériau statique, de réaliser des stratifications, etc. Il y a trop de possibilités à mentionner, certains plus que d'autres. Par exemple, j'utilise l'approche pour simuler l'animation de contenu se déplaçant d'un emplacement de grille à un autre - faire le placement réel à l'événement d'achèvement de l'animation. Bonne chance.

6
répondu Redsplinter 2009-10-30 01:00:23

Essentiellement, il nécessite une réécriture complète de la Toile. Les solutions proposées antérieurement qui supplantent MeasureOverride échouent parce que la toile par défaut.Gauche./Les propriétés de Top & c invalident L'arrangement, mais doivent également invalider la mesure. (Vous obtenez la bonne taille la première fois, mais la taille ne change pas si vous déplacez des éléments après la mise en page initiale).

la solution de grille est plus-ou moins raisonnable mais liant aux marges afin d'obtenir x-y le déplacement peut se déclencher ravages sur d'autres codes (notamment dans MVVM). J'ai eu du mal avec la solution de vue sur Grille pendant un certain temps, mais les complications avec les interactions vue/Modèle et le comportement de défilement finalement m'ont conduit à cela. Ce qui est simple et direct, et ça marche.

il n'est pas si compliqué de mettre en œuvre à nouveau arrangement override et mesure override. Et vous êtes obligé d'écrire au moins autant de code ailleurs traitant de la stupidité de la grille/marge. Si vous y êtes.

une solution plus complète. le comportement de marge non nulle n'a pas été testé. Si vous avez besoin de quoi que ce soit d'autre que Left and Top, alors cela fournit un point de départ, au moins.

avertissement: vous devez utiliser AutoResizeCanvas.Gauche et AutoResizeCanvas.TOP propriétés attachées au lieu de toile.Gauche et de la Toile.Haut. Les propriétés restantes de Canvas n'ont pas été implémentées.

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



    }


}
6
répondu Robin Davies 2014-02-03 17:14:14

liant la hauteur / largeur à la taille réelle de la commande dans la toile travaillée pour moi:

        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
            <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
                    Width="{Binding ElementName=myListBox, Path=ActualWidth}">
                <ListBox x:Name="myListBox" />
            </Canvas>
        </ScrollViewer>
2
répondu user500099 2012-06-20 12:22:24
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;
}

dans la fonction, j'essaie de trouver la position X maximale et la position Y, où les commandes dans la toile peuvent résider.

utiliser la fonction uniquement en cas de charge ou plus tard et pas dans le constructeur. La fenêtre doit être mesurée avant le chargement..

1
répondu Sayka 2015-03-18 01:32:06

comme une amélioration à la réponse de @MikeKulls, voici une version qui ne jette pas une exception quand il n'y a pas D'éléments UI dans la toile ou quand il y a des éléments UI sans Toile.Top ou de la Toile.Propriétés à gauche:

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);
    }
}
1
répondu Asaf 2015-07-15 18:13:41

j'ai également rencontré ce problème, mon problème était que la grille ne se redimensionnait pas automatiquement lorsque la toile se redimensionnait grâce à la fonction MeasureOverride.

mon problème: WPF Measureverride loop

0
répondu Ori Frish 2017-05-23 12:34:23

j'ai pu obtenir le résultat que vous recherchez simplement en ajoutant un nouvel événement modifié de taille à la commande qui contenait les données qui causaient la toile de se développer. Une fois que la toile atteint l'étendue de la fenêtre de défilement, les barres de défilement apparaissent. Je viens d'attribuer l'expression lambda suivante à l'événement de changement de taille de la commande:

text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height; 
                                 DrawingCanvas.Width = e.NewSize.Width; };
0
répondu Hyperion753 2018-05-02 22:58:28
<viewbox>
    <canvas>
        <uielements /> 
    </canvas>
</viewbox>
-3
répondu Joe 2011-11-13 00:35:32