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, хотя он не обрезает своих детей.
11 ответов
нет это невозможно (см. Фрагмент из MSDN ниже). Однако, если вы хотите иметь полосы прокрутки и автоматическое изменение размера, рассмотрите возможность использования решетки вместо этого и используйте свойство Margin для размещения элементов в этой сетке.. Сетка скажет ScrollViewer, насколько он хочет быть большим, и вы получите полосы прокрутки.. Canvas всегда говорит ScrollViewer, что ему не нужен размер.. :)
сетка позволяет вам наслаждаться обоими мирами - до тех пор, пока вы помещаете все элементы в одиночная клетка, вы получаете оба: произвольное располагать и автоматическ-определять размер. В общем, хорошо помнить, что большинство панельных элементов управления (DockPanel, StackPanel и т. д.) могут быть реализованы с помощью элемента управления Grid.
с MSDN:
Canvas-это единственный элемент, который не имеет собственных характеристик макета. Холст по умолчанию имеет свойства Height и Width равными нулю, если только он не является дочерним элементом элемента, который автоматически определяет размер его дочерних элементов. Дочерний элемент холст никогда не изменяется, они просто расположены в назначенных координатах. Это обеспечивает гибкость для ситуаций, в которых присущие ограничения калибровки или выравнивание не нужны или не нужны. В случаях, когда требуется автоматическое изменение размера и выравнивание дочернего содержимого, обычно лучше использовать элемент сетки.
надеюсь, что это помогает
Я просто копирую ответ иллефа здесь, но в ответ на 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>
Я предпочитаю это решение приведенному выше, которое использует сетку, поскольку она работает через вложенные свойства и просто требует установки меньших свойств на элементах.
Я думаю, вы можете изменить размер Canvas
переопределив MeasureOverride
или ArrangeOverride
методы.
эта работа не сложная.
вы можете увидеть этот пост. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer
Я надеюсь, это поможет вам.
спасибо.
Я вижу, у вас есть работоспособное решение, но я подумал, что поделюсь.
<Canvas x:Name="topCanvas">
<Grid x:Name="topGrid" Width="{Binding ElementName=topCanvas, Path=ActualWidth}" Height="{Binding ElementName=topCanvas, Path=ActualHeight}">
...Content...
</Grid>
</Canvas>
вышеуказанный метод позволит вам вложить сетку внутри холста и иметь динамическое изменение размера. Дальнейшее использование размерной привязки позволяет смешивать динамический материал со статическим, выполнять наслаивание и др. Есть слишком много возможностей, чтобы упомянуть, некоторые сложнее, чем другие. Например, я использую подход для имитации анимации контента, перемещающегося из одного местоположения сетки в другое-выполнение фактическое размещение в событии завершения анимации. Удача.
по существу это требует полной переписывания холста. Предыдущие предлагаемые решения, переопределяющие MeasureOverride, завершаются ошибкой из-за холста по умолчанию.Влево./Свойства Top &c аннулируют аранжировку, но также должны аннулировать меру. (Вы получаете правильный размер в первый раз, но размер не меняется, если вы перемещаете элементы после первоначального макета).
решение сетки более или менее разумно, но привязка к полям, чтобы получить смещение x-y, может нанести ущерб другим код (particalary в MVVM). Некоторое время я боролся с решением Grid view, но осложнения с взаимодействиями View/ViewModel и поведением прокрутки, наконец, довели меня до этого. Что просто и по делу, и просто работает.
не так сложно повторно реализовать ArrangeOverride и MeasureOverride. И вы обязаны написать по крайней мере столько же кода в другом месте, связанном с глупостью сетки/маржи. Так что у вас есть.
вот более полное решение. не ноль Маржинальное поведение не проверено. Если вам нужно что-то другое, кроме левого и верхнего, то это обеспечивает отправную точку, по крайней мере.
предупреждение: Вы должны использовать AutoResizeCanvas.Слева и AutoResizeCanvas.Верхние вложенные свойства вместо Canvas.Left и Canvas.Верхний. Остальные свойства 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));
}
}
привязка высоты / ширины к фактическому размеру элемента управления в холсте работала для меня:
<ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
<Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
Width="{Binding ElementName=myListBox, Path=ActualWidth}">
<ListBox x:Name="myListBox" />
</Canvas>
</ScrollViewer>
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 event или более поздней версии, а не в конструкторе. Окно должно быть измерено перед загрузкой..
в качестве улучшения ответа @MikeKulls, вот версия, которая не вызывает исключения, когда в холсте нет элементов пользовательского интерфейса или когда есть элементы пользовательского интерфейса без холста.Верх или холст.Левые свойства:
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);
}
}
Я также столкнулся с этой проблемой, моя проблема заключалась в том, что сетка не изменялась автоматически, когда холст изменял размер благодаря переопределенной функции MeasureOverride.
моя проблема: цикл MeasureOverride WPF
я смог достичь результата, который вы ищете, просто добавив новое событие изменения размера в элемент управления, который содержал данные, которые заставляли холст расти. После того, как холст достигнет экстента средства просмотра прокрутки, появятся полосы прокрутки. Я просто назначил следующее лямбда-выражение событию изменения размера элемента управления:
text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height;
DrawingCanvas.Width = e.NewSize.Width; };