WPF: Как сделать авто-изменение размера холста? - PullRequest
41 голосов
/ 13 мая 2009

Я бы хотел, чтобы мой Canvas автоматически изменил размер его элементов, чтобы полосы прокрутки ScrollViewer имели правильный диапазон. Можно ли это сделать в 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>

В приведенном выше коде холст всегда имеет размер 0, хотя и не обрезает его потомков.

Ответы [ 11 ]

48 голосов
/ 14 мая 2009

Нет, это невозможно (см. Фрагмент из MSDN ниже). Однако, если вы хотите иметь полосы прокрутки и автоматическое изменение размера, попробуйте вместо этого использовать Grid и используйте свойство Margin для позиционирования ваших элементов в этой сетке. Сетка сообщит ScrollViewer, насколько большим он будет , и вы получите полосы прокрутки. Canvas всегда сообщает ScrollViewer, что ему не нужен какой-либо размер ..:)

Сетка позволяет вам наслаждаться обоими мирами. Пока вы помещаете все элементы в одну ячейку, вы получаете и другое: произвольное позиционирование и автоматическое изменение размера. В общем, хорошо помнить, что большинство элементов управления панели (DockPanel, StackPanel и т. Д.) Могут быть реализованы с помощью элемента управления Grid.

С MSDN :

Canvas - единственный элемент панели, который не имеет внутренних характеристик макета. Canvas имеет свойства высоты и ширины по умолчанию, равные нулю, если только он не является дочерним элементом элемента, который автоматически изменяет размеры своих дочерних элементов. Дочерние элементы Canvas никогда не меняются, они просто располагаются по назначенным координатам. Это обеспечивает гибкость в ситуациях, когда внутренние ограничения или выравнивание размеров не нужны или не нужны. Для случаев, когда вы хотите, чтобы дочерний контент автоматически изменял размеры и выравнивал, обычно лучше использовать элемент Grid.

Надеюсь, это поможет

36 голосов
/ 19 августа 2011

Я просто копирую здесь ответ Илефа, но в ответ на PilotBob вы просто определяете объект холста, как этот

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

, а затем используйте CanvasAutoSize в вашем XAML.

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

Я предпочитаю это решение, представленное выше, которое использует сетку, поскольку она работает через присоединенные свойства и просто требует установки меньшего количества свойств для элементов.

9 голосов
/ 15 июля 2010

Я думаю, вы можете изменить размер Canvas, переопределив MeasureOverride или ArrangeOverride методы

Эта работа не сложная.

Вы можете увидеть это сообщение. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Надеюсь, это поможет вам.

Спасибо.

6 голосов
/ 03 февраля 2014

По сути это требует полного переписывания Canvas. Предыдущие предложенные решения, которые переопределяют MeasureOverride, терпят неудачу, потому что свойства Canvas.Left / .Top & c по умолчанию делают недействительным Arrangment, но ТАКЖЕ нужно аннулировать меру. (Вы получаете правильный размер в первый раз, но размер не изменится, если вы переместите элементы после первоначального макета).

Решение Grid более или менее разумно, но привязка к полям для получения смещения по оси x может нанести ущерб другому коду (частично в MVVM). Некоторое время я боролся с решением Grid View, но сложности с взаимодействиями View / ViewModel и поведением прокрутки, наконец, привели меня к этому. Что просто и по существу, и просто работает.

Нет ничего сложного в том, чтобы повторно реализовать ArrangeOverride и MeasureOverride. И вы должны написать хотя бы столько же кода в другом месте, касающегося глупости Grid / Margin. Итак, вы здесь.

Вот более полное решение. ненулевое маржинальное поведение не проверено. Если вам нужно что-то кроме Left и Top, то это, по крайней мере, отправная точка.

ВНИМАНИЕ: Вы должны использовать присоединенные свойства AutoResizeCanvas.Left и AutoResizeCanvas.Top вместо Canvas.Left и Canvas.Top. Остальные свойства Canvas не были реализованы.

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 голосов
/ 30 октября 2009

Я вижу, у вас есть работоспособное решение, но я решил поделиться.

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

Вышеупомянутая техника позволит вам вложить сетку в холст и иметь динамическое изменение размера. Дальнейшее использование привязки размеров позволяет смешивать динамический материал со статическим материалом, выполнять наслоение и т. Д. Можно упомянуть слишком много возможностей, некоторые из которых сложнее других. Например, я использую подход для имитации анимации контента, перемещающегося из одного местоположения сетки в другое - делая фактическое размещение в событии завершения анимации. Удачи.

2 голосов
/ 20 июня 2012

Привязка высоты / ширины к фактическому размеру элемента управления внутри холста сработала для меня:

        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
            <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
                    Width="{Binding ElementName=myListBox, Path=ActualWidth}">
                <ListBox x:Name="myListBox" />
            </Canvas>
        </ScrollViewer>
1 голос
/ 15 июля 2015

В качестве улучшения ответа @ MikeKulls, вот версия, которая не выдает исключение, когда на холсте нет элементов пользовательского интерфейса или когда есть элементы пользовательского интерфейса без свойств Canvas.Top или 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);
    }
}
1 голос
/ 18 марта 2015
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;
}

В функции я пытаюсь найти максимальную позицию X и позицию Y, где могут находиться элементы управления на холсте.

Используйте функцию только в событии Loaded или позже, а не в конструкторе. Перед загрузкой окно должно быть измерено.

0 голосов
/ 03 мая 2018

Мне удалось достичь желаемого результата, просто добавив новое событие изменения размера в элемент управления, который содержал данные, которые вызывали рост холста. После того, как холст достигнет уровня средства просмотра прокрутки, появятся полосы прокрутки. Я просто назначил следующее лямбда-выражение событию изменения размера элемента управления:

text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height; 
                                 DrawingCanvas.Width = e.NewSize.Width; };
0 голосов
/ 09 июня 2015

Я также столкнулся с этой проблемой, моя проблема заключалась в том, что сетка не меняла размеры автоматически, когда Canvas изменял размер благодаря переопределенной функции MeasureOverride.

моя проблема: WPF MeasureOverride loop

...