2009-05-12 10 views
38

Tôi muốn Canvas để tự động thay đổi kích thước về kích thước của các mục của nó, sao cho thanh cuộn ScrollViewer có phạm vi chính xác. Điều này có thể được thực hiện trong XAML?WPF: Làm thế nào để làm cho canvas tự động thay đổi kích cỡ?

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

Trong mã ở trên, canvas luôn có kích thước 0, mặc dù nó không cắt con của nó.

Trả lời

45

Không thể thực hiện điều này (xem đoạn mã từ MSDN bên dưới). Tuy nhiên, nếu bạn muốn có thanh cuộn và tự động thay đổi kích thước, hãy cân nhắc sử dụng một Lưới thay thế và sử dụng thuộc tính Lề để định vị các mục của bạn trên Lưới này .. Lưới sẽ cho ScrollViewer biết mức độ mong muốn của bạn và bạn sẽ lấy thanh cuộn .. Canvas sẽ luôn báo cho ScrollViewer rằng anh ta không cần bất kỳ kích thước nào .. :)

Lưới cho phép bạn tận hưởng cả hai thế giới - Miễn là bạn đang đưa tất cả các phần tử vào một ô, bạn sẽ nhận được cả hai : Định vị tùy ý và tự động định cỡ. Nói chung nó là tốt để nhớ rằng hầu hết các bảng điều khiển (DockPanel, StackPanel, vv) có thể được thực hiện thông qua một điều khiển lưới.

Từ MSDN:

Canvas là yếu tố bảng duy nhất mà không có đặc điểm bố trí vốn có. Canvas có các thuộc tính Chiều cao và Chiều rộng mặc định bằng 0, trừ khi nó là phần tử con của phần tử tự động kích thước các phần tử con của nó. Các phần tử con của Canvas không bao giờ được thay đổi kích thước, chúng chỉ được định vị ở tọa độ được chỉ định của chúng. Điều này cung cấp sự linh hoạt cho các tình huống mà trong đó các ràng buộc định cỡ cố định hoặc căn chỉnh là không cần thiết hoặc muốn. Đối với các trường hợp bạn muốn nội dung con được tự động thay đổi kích thước và căn chỉnh, tốt nhất là sử dụng phần tử Lưới.

Hope this helps

+5

Tôi đã chuyển từ Canvas sang Grid và hoạt động sau khi chỉnh sửa. Tôi đã phải thực hiện hai thay đổi: (1) ở khắp mọi nơi mà tôi đã sử dụng để thiết lập thuộc tính đính kèm Canvas.Left và Canvas.Top, bây giờ tôi thiết lập các thuộc tính thông thường Margin.Left và Margin.Top (Margin.Right và Margin.Bottom có ​​thể trái tại 0); (2) sử dụng HorizontalAlignment = "Left" và VerticalAlignment = "Top" trên mỗi phần tử trong Grid. Chế độ "Stretch" mặc định có thể khiến các phần tử kết thúc ở giữa khi lề là 0. – Qwertie

+2

"Đối với trường hợp bạn muốn nội dung con được tự động thay đổi kích thước và căn chỉnh, tốt nhất là sử dụng phần tử Lưới". Nhưng câu hỏi ban đầu là về việc thay đổi kích thước Canvas, không phải là các phần tử con.Tôi nghĩ rằng giải pháp được cung cấp bởi illef dưới đây trả lời tốt hơn câu hỏi này và tránh thiết lập quá nhiều thuộc tính trên tất cả các phần tử con. Với câu trả lời của illef bạn chỉ cần thiết lập thuộc tính đính kèm Top và Left mà tôi nghĩ là một giải pháp neater. Một khi bạn đã xác định đối tượng Canvas mới thì đó là một giải pháp có thể tái sử dụng có thể được sử dụng ở nơi khác trong dự án của bạn. – MikeKulls

+0

Vấn đề tôi có với điều này là khi kết xuất nó sẽ không điều khiển chồng lên nhau và chúng bị lúng túng khắp nơi, trừ khi tất nhiên tôi đang làm điều gì đó sai. Ngoài ra các giải pháp illef là tính toán sai cho một số lý do. –

6

tôi thấy bạn đã có một giải pháp khả thi, nhưng tôi nghĩ rằng tôi muốn chia sẻ.

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

Kỹ thuật trên sẽ cho phép bạn lồng lưới trong canvas và thay đổi kích thước động. Sử dụng thêm kích thước ràng buộc làm cho nó có thể trộn vật liệu động với vật liệu tĩnh, thực hiện phân lớp, vv Có quá nhiều khả năng đề cập đến, một số khó khăn hơn những người khác. Ví dụ: tôi sử dụng cách tiếp cận để mô phỏng nội dung hoạt ảnh chuyển động từ vị trí lưới này sang vị trí lưới khác - thực hiện vị trí thực tế tại sự kiện hoàn thành hoạt ảnh. Chúc may mắn.

-3
<viewbox> 
    <canvas> 
     <uielements /> 
    </canvas> 
</viewbox> 
+4

Một chút xáo trộn trong một đoạn văn sẽ tốt đẹp hơn ngoài câu trả lời. – slm

+1

điều này sẽ không hoạt động – Liero

9

Tôi nghĩ bạn có thể thay đổi kích thước bằng cách ghi đè CanvasMeasureOverride hoặc ArrangeOverride phương pháp.

Công việc này không khó.

Bạn có thể xem bài đăng này. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

Tôi hy vọng điều này sẽ giúp ích cho bạn.

Cảm ơn bạn.

+0

Tôi cần thực hiện việc này, nhưng tôi gặp sự cố với mã được trình bày. Chính xác những gì bạn có nghĩa là "xác định canvas mới". Bạn có nghĩa là một lớp xuất phát từ Canvas? Nếu vậy, tôi nhận được không chứa def cho InternalChildren và không thể ghi đè lên thành viên được thừa kế. – PilotBob

+0

Xem câu trả lời của tôi bên dưới – MikeKulls

+0

+1 Ý tưởng tuyệt vời về cách giải quyết yêu cầu! Ngoài ra, tôi sẽ đề xuất mở rộng mã của bạn bằng kiểm tra double.IsNaN- cho các giá trị trên cùng và bên trái và đặt chúng thành 0 nếu chúng là NaN. – HCL

32

Tôi chỉ cần sao chép câu trả lời illef ở đây nhưng trong câu trả lời cho PilotBob, bạn chỉ cần xác định một đối tượng canvas như thế này

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

và sau đó sử dụng CanvasAutoSize trong XAML của bạn.

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

Tôi thích giải pháp này cho giải pháp ở trên sử dụng lưới khi nó hoạt động thông qua các thuộc tính đính kèm và chỉ yêu cầu đặt ít thuộc tính hơn trên các phần tử.

+0

làm thế nào đến tôi nhận được lỗi thời gian chạy về 'chiều rộng' và 'chiều cao' trong MeasureOverride nói rằng cả hai 'chiều rộng' và 'chiều cao' là NaN – jondinham

+2

tôi phát hiện ra lý do tại sao, tất cả các yếu tố trong CanvasAutoSize phải có Canvas.Left & Canvas.Top bộ – jondinham

+4

Tôi đồng ý, đây là giải pháp tốt hơn nhiều so với việc sử dụng lưới nếu bạn đang làm bất kỳ điều gì phức tạp về bố cục. Ví dụ, tôi đã được ràng buộc chiều cao của một điều khiển khác, và do đó, thêm vào một lề đã gây ra một số loại đệ quy bố trí vô hạn. Như Paul Dinh nói mặc dù, giải pháp này sẽ bị hỏng nếu bạn không đặt Canvas.Left và Canvas.Top trên mọi mục, điều này gây phiền nhiễu nếu bạn có một số ở (0, 0). Tôi đã thay đổi phần thân của Max() lambda để gán Canvas.Left/Top thành double và kiểm tra double.IsNaN(), và nếu sử dụng 0.0 thay thế. Điều đó hoạt động rất tốt. –

2

Binding Chiều cao/Chiều rộng với kích thước thực tế của sự kiểm soát bên trong vải làm việc cho tôi:

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

Tôi đoán rằng nó hoạt động nếu bạn chỉ cần một đứa trẻ duy nhất để kiểm soát kích thước canvas. Nhưng tại sao đặt ListBox trên Canvas trên ScrollViewer thay vì chỉ sử dụng ListBox một mình? – Qwertie

5

Về cơ bản nó đòi hỏi một viết lại hoàn toàn Canvas. Các giải pháp được đề xuất trước đây ghi đè MeasureOverride không thành công vì các thuộc tính mặc định Canvas.Left/.Top & c vô hiệu hóa Sắp xếp, nhưng CSONG cần phải làm mất hiệu lực biện pháp. (Lần đầu tiên bạn nhận được kích thước phù hợp, nhưng kích thước không thay đổi nếu bạn di chuyển các phần tử sau bố cục ban đầu).

Giải pháp Grid ít nhiều hợp lý nhưng ràng buộc với Lề để có được sự dịch chuyển x-y có thể phá hoại mã khác (particalary trong MVVM). Tôi đã vật lộn với giải pháp xem Grid trong một thời gian, nhưng các biến chứng với tương tác View/ViewModel và hành vi di chuyển cuối cùng đã khiến tôi thực hiện điều này. Đó là đơn giản và cho điểm, và chỉ cần làm việc.

Không phức tạp khi triển khai lại ArrangeOverride và MeasureOverride. Và bạn buộc phải viết ít nhất là nhiều mã ở nơi khác xử lý Grid/Margin ngu ngốc. Vì vậy, có bạn đang có.

Đây là giải pháp hoàn chỉnh hơn. hành vi ký quỹ khác không được kiểm tra. Nếu bạn cần bất cứ điều gì khác hơn là trái và đầu, thì điều này cung cấp một điểm khởi đầu, ít nhất.

CẢNH BÁO: Bạn phải sử dụng AutoResizeCanvas.Left và AutoResizeCanvas.Top các thuộc tính được đính kèm thay vì Canvas.Left và Canvas.Top. Các thuộc tính Canvas còn lại chưa được triển khai.

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



    } 


} 
+1

Biết khi từ bỏ một giải pháp hackish là vô giá! Triển khai bảng tùy chỉnh là giải pháp đúng trong nhiều trường hợp. – mydogisbox

+0

Đây là câu trả lời hay nhất – Tuco

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

Trong chức năng, tôi đang cố gắng để tìm ra vị trí X tối đa và vị trí Y, nơi điều khiển trong vải có thể cư trú.

Chỉ sử dụng chức năng trong sự kiện được tải hoặc sau đó chứ không phải trong hàm tạo. Cửa sổ phải được đo trước khi tải ..

0

Tôi cũng gặp sự cố này, vấn đề của tôi là lưới không tự động thay đổi kích thước khi Canvas đã thay đổi kích thước nhờ chức năng MeasureOverride bị ghi đè.

vấn đề của tôi: WPF MeasureOverride loop

0

Như một sự cải tiến để @ câu trả lời MikeKulls của, đây là một phiên bản mà không ném một ngoại lệ khi không có các yếu tố giao diện người dùng trong vải hoặc khi có những yếu tố giao diện người dùng mà không cần Canvas.Top hoặc Thuộc tính 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); 
    } 
}