WPF:如何使canvas自动resize?

我想我的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> 

在上面的代码中,canvas总是具有0的大小,尽pipe它不剪切它的子元素。

不,这是不可能的(见下面的MSDN片段)。 但是,如果你想有滚动条和自动resize,可以考虑使用Grid来代替,并使用Margin属性来定位你的项目在这个Grid上。Grid会告诉ScrollViewer他想要的大小,然后你将得到滚动条..canvas将始终告诉ScrollViewer他不需要任何大小.. 🙂

网格让你享受这两个世界 – 只要你把所有的元素放到一个单元格中,你就会得到:任意定位和自动resize。 一般来说,最好记住大多数面板控件(DockPanel,StackPanel等)都可以通过Grid控件来实现。

来自MSDN :

canvas是唯一不具有固有布局特性的面板元素。 Canvas的默认高度和宽度属性为零,除非它是自动调整其子元素的元素的子元素。 Canvas的子元素不会resize,只能放在指定的坐标上。 这为不需要或不需要固有尺寸约束或alignment的情况提供了灵活性。 对于希望子内容自动resize和alignment的情况,通常最好使用Grid元素。

希望这可以帮助

我只是在这里复制非法的答案,但在答复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); } } 

然后在你的XAML中使用CanvasAutoSize。

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

我更喜欢这个解决scheme,上面提到的那个使用网格的方法,因为它通过附加的属性工作,只需要在元素上设置较less的属性。

我想你可以通过重写MeasureOverrideArrangeOverride方法来调整Canvas的大小。

这份工作并不难。

你可以看到这个post。 http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

我希望这可以帮助你。

谢谢。

我看到你有一个可行的解决scheme,但我想我会分享。

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

上述技术将允许您在canvas中嵌套网格并进行dynamicresize。 进一步使用尺寸约束可以将dynamic材料与静态材料混合,执行分层等。提到的可能性太多,比其他一些更难。 例如,我使用这种方法来模拟animation从一个网格位置移动到另一个网格位置的内容 – 在animation的完成事件中进行实际的位置。 祝你好运。

本质上它需要完全重写Canvas。 先前提出的覆盖MeasureOverride的解决scheme失败,因为默认的Canvas.Left / .Top&c属性使Arrangment失效,但是也需要使measure失效。 (你第一次得到合适的大小,但是如果你在初始布局之后移动元素,大小不会改变)。

Grid解决scheme或多或less是合理的,但为了得到xy位移而绑定到Margins会对其他代码造成严重破坏(MVVM中的特殊情况)。 我在网格视图解决scheme中苦苦挣扎了一段时间,但View / ViewModel交互和滚动行为的复杂性最终导致我这样做。 这是简单的,重点,只是工程。

重新实现ArrangeOverride和MeasureOverride并不复杂。 而且你必须在其他地方编写至less与Grid / Margin愚蠢相关的代码。 所以你就是这样

这是一个更完整的解决scheme。 非零保证金行为未经testing。 如果您需要“左”和“上”以外的任何其他内容,那么至less可以提供一个起点。

警告:您必须使用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)); } } 

将高度/宽度绑定到canvas内的控件的实际大小适用于我:

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

在函数中,我试图find最大的X位置和Y位置,在canvas中的控件可以驻留。

仅在Loaded事件或更高版本中使用该函数,而不在构造函数中使用。 加载前必须测量窗口

我也遇到了这个问题,我的问题是,当Canvas调整了MeasureOverride函数的大小时,网格不能自动resize。

我的问题: WPF MeasureOverride循环

作为对@ MikeKulls的回答的一个改进,这里有一个版本,当canvas中没有UI元素或没有Canvas.Top或Canvas.Left属性的UI元素时,不会抛出exception:

 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); } } 
 <viewbox> <canvas> <uielements /> </canvas> </viewbox>