Подтвердить что ты не робот

WPF: Как сделать автоматическое изменение размера холста?

Я бы хотел, чтобы мой 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, хотя он не обрезает его дочерние элементы.

4b9b3361

Ответ 1

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

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

От MSDN:

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

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

Ответ 2

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

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>

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

Ответ 3

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

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

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

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

Спасибо.

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

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

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

        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
            <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
                    Width="{Binding ElementName=myListBox, Path=ActualWidth}">
                <ListBox x:Name="myListBox" />
            </Canvas>
        </ScrollViewer>

Ответ 7

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

Ответ 8

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

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

Ответ 9

В качестве улучшения для @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);
    }
}

Ответ 10

<viewbox>
    <canvas>
        <uielements /> 
    </canvas>
</viewbox>