wake-up-neo.com

WPF: Wie kann ich die Leinwand automatisch verkleinern lassen?

Ich möchte, dass meine Canvas automatisch auf die Größe der Elemente angepasst wird, sodass die ScrollViewer-Bildlaufleisten den richtigen Bereich haben. Kann das in XAML gemacht werden?

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

Im obigen Code hat die Leinwand immer die Größe 0, die untergeordneten Elemente werden jedoch nicht beschnitten.

41
Qwertie

Nein, dies ist nicht möglich (siehe Abschnitt aus MSDN unten). Wenn Sie jedoch Bildlaufleisten und eine automatische Größenanpassung wünschen, ziehen Sie die Verwendung von Grid in Betracht und verwenden Sie die Margin-Eigenschaft, um Ihre Elemente in diesem Gitter zu positionieren Sie erhalten die Scrollbalken. Canvas sagt dem ScrollViewer immer, dass er keine Größe benötigt. :)

Mit dem Raster können Sie beide Welten genießen. Solange Sie alle Elemente in eine einzige Zelle einfügen, erhalten Sie beides: beliebige Positionierung und automatische Größenanpassung. Im Allgemeinen ist es gut zu wissen, dass die meisten Bedienelemente (DockPanel, StackPanel usw.) über ein Grid-Steuerelement implementiert werden können.

Von MSDN :

Canvas ist das einzige Panelelement, das keine inhärenten Layoutmerkmale aufweist. Ein Canvas hat standardmäßig die Eigenschaften Height und Width von Null, es sei denn, er ist das untergeordnete Element eines Elements, das seine untergeordneten Elemente automatisch in der Größe ändert. Untergeordnete Elemente eines Canvas-Bereichs werden niemals in der Größe geändert, sondern nur an den angegebenen Koordinaten positioniert. Dies bietet Flexibilität für Situationen, in denen inhärente Größenbeschränkungen oder -ausrichtungen nicht erforderlich sind oder gewünscht werden. In Fällen, in denen Sie möchten, dass die Größe und Ausrichtung von untergeordneten Inhalten automatisch geändert wird, empfiehlt es sich, ein Grid-Element zu verwenden.

Hoffe das hilft

48
Arcturus

Ich kopiere hier nur die Antwort von illegal, aber als Antwort auf PilotBob definieren Sie einfach ein Canvas-Objekt wie dieses

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

und dann verwenden Sie CanvasAutoSize in Ihrer XAML.

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

Ich bevorzuge diese Lösung der oben dargestellten, die das Raster verwendet, da es durch angefügte Eigenschaften arbeitet und nur weniger Eigenschaften für die Elemente festlegen muss.

35
MikeKulls

Ich denke, Sie können Canvas durch Überschreiben von MeasureOverride- oder ArrangeOverride-Methoden verändern. 

Dieser Job ist nicht schwierig. 

Sie können diesen Beitrag sehen. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Ich hoffe das hilft dir. 

Vielen Dank. 

9
illef

Im Wesentlichen muss Canvas vollständig neu geschrieben werden. Vorherige vorgeschlagene Lösungen, die MeasureOverride überschreiben, schlagen fehl, weil die Standardeigenschaften Canvas.Left/.Top & c Arrangment ungültig machen, aber auch die Maßnahme muss ungültig gemacht werden. (Sie erhalten beim ersten Mal die richtige Größe. Die Größe ändert sich jedoch nicht, wenn Sie Elemente nach dem ursprünglichen Layout verschieben.).

Die Grid-Lösung ist mehr oder weniger vernünftig, bindet jedoch an Margins, um eine Verschiebung von x-y zu erreichen, die anderen Code (besonders in MVVM) verheerend wirken kann. Ich hatte eine Zeit lang Probleme mit der Grid-Ansichtslösung, aber Komplikationen bei View/ViewModel-Interaktionen und Scrollverhalten trieben mich schließlich dazu. Welches ist einfach und auf den Punkt und Just Works.

Es ist nicht so kompliziert, ArrangeOverride und MeasureOverride neu zu implementieren. Und Sie müssen mindestens genauso viel Code an anderer Stelle schreiben, in dem es um Dummheit bei Grid/Margin geht. Also da bist du.

Hier ist eine vollständigere Lösung. Nicht-Null-Margin-Verhalten wurde nicht getestet. Wenn Sie etwas anderes als Left und Top benötigen, bietet dies zumindest einen Startpunkt.

WARNUNG: Sie müssen AutoResizeCanvas.Left und AutoResizeCanvas.Top angefügte Eigenschaften anstelle von Canvas.Left und Canvas.Top verwenden. Die übrigen Canvas-Eigenschaften wurden nicht implementiert.

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

Ich sehe, dass Sie eine praktikable Lösung haben, aber ich dachte, ich würde sie teilen.

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

Die obige Technik ermöglicht es Ihnen, ein Raster in eine Leinwand zu schachteln und dynamische Größenänderungen vorzunehmen. Die weitere Verwendung der Dimensionsbindung ermöglicht das Mischen von dynamischem Material mit statischem Material, das Durchführen von Schichten usw. Es gibt zu viele Möglichkeiten, einige zu erwähnen, manche sind schwieriger als andere. Zum Beispiel verwende ich den Ansatz, um animierte Inhalte zu simulieren, die von einem Rasterplatz zu einem anderen verschoben werden - die eigentliche Platzierung beim Abschlussereignis der Animation. Viel Glück.

6
Redsplinter

Binden der Höhe/Breite an die tatsächliche Größe des Steuerelements innerhalb der für mich bearbeiteten Leinwand

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

In der Funktion versuche ich, die maximale X-Position und Y-Position zu finden, an der sich Steuerelemente auf der Leinwand befinden können.

Verwenden Sie die Funktion nur im geladenen Ereignis oder später und nicht im Konstruktor. Das Fenster muss vor dem Laden vermessen werden.

1
Sayka

Als eine Verbesserung der Antwort von @ MikeKulls, hier eine Version, die keine Ausnahme auslöst, wenn keine Elemente der Benutzeroberfläche in der Zeichenfläche vorhanden sind oder wenn Oberflächenelemente ohne Canvas.Top oder Canvas.Left-Eigenschaften vorhanden sind

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
Asaf

Ich konnte das gewünschte Ergebnis erzielen, indem ich dem Steuerelement, das die Daten enthielt, die die Leinwand vergrößerten, einfach ein Ereignis mit geänderter Größe hinzufügte. Nachdem die Leinwand die Ausdehnung der Bildlaufanzeige erreicht hat, werden die Bildlaufleisten angezeigt. Ich habe gerade den folgenden Lambda-Ausdruck dem Ereignis mit geänderter Größe des Steuerelements zugewiesen:

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

Dieses Problem ist auch aufgetreten. Mein Problem war, dass das Raster nicht automatisch angepasst wurde, wenn die Größe des Canvas-Bereichs dank der überschriebenen MeasureOverride-Funktion geändert wurde.

mein Problem: WPF MeasureOverride-Schleife

0
Ori Frish