Expanding Canvas 만들기




목차


앞선 포스팅(http://crystalcube.co.kr/153)에서 DraggableCanvas 를 만들어 보았습니다. 그런데 조금 불편한게 있습니다.

Canvas 밖으로 드래그를 했을 때, Canvas 가 자동으로 늘어나지 않기 때문에 찾을 방법이 없어집니다. 윈도우 자체의 크기를 늘려서 Canvas 를 늘리지 않는 한 말이죠.




개요


Canvas 의 Element(Child)가 Canvas 밖으로 빠져나가면, Grid 처럼 Canvas 의 사이즈가 거기에 맞게 커지도록 해 보겠습니다. 즉 확장되는 캔버스 라고 할 수 있겠네요.


이 방법에 대해서 고민을 많이 해 보았습니다.


1 차 시도.

Canvas 의 Template 을 손댄뒤, 크기를 Binding 을 통해 무언가 시도해 본다.

여러가지 해 보았지만, 깔끔하게 떨어지지 않더군요... 다른 방법을 찾아봐야 겠습니다.


2 차 시도.

Canvas 의 MeasureOverride 에서 크기를 다시 계산한다.

MeasureOverride 함수가 적절한 시기에 계속해서 호출되어야 하는데, 이게 안됩니다.

OnRender 함수가 호출이 매번 되는 것도 아니고...

어디선가 Child 의 위치/크기가 변경될때마다 열심히 호출해 주어야 하는데 적절한 위치가 없네요.


3 차 시도.

MeasureOverride 를 이용하되, PropertyMetaData 를 Override 하여, AffectsParentMeasure 옵션을 넣어준다.

잘 동작합니다 :)


어차피 Canvas 안에서 위치가 이동하려면, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom 들이 바뀌어야 하지요.(물론 이외의 방법도 있을 수 있기에,, 고려해야 하지만, 여기서는 무시하도록 하겠습니다 -_-)

즉, LeftProperty, RightProperty 등을 override 해 주면 됩니다.







코드는 다음과 같습니다.



    [ExpandingCanvas.cs]



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using SW.Common.Extensions;


namespace SW.Common.Controls
{
    public class ExpandingCanvas : Canvas
    {
        static ExpandingCanvas()
        {
            if(DesignerProperties.GetIsInDesignMode(new DependencyObject()) == false)
            {
                Canvas.LeftProperty.OverrideMetadata(typeof(UIElement), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
                Canvas.TopProperty.OverrideMetadata(typeof(UIElement), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
                Canvas.RightProperty.OverrideMetadata(typeof(UIElement), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
                Canvas.BottomProperty.OverrideMetadata(typeof(UIElement), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
            }
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpandingCanvas), new FrameworkPropertyMetadata(typeof(ExpandingCanvas)));
        }



        protected override Size MeasureOverride(Size constraint)
        {
            List<FrameworkElement> children = this.FindVisualDescendants<FrameworkElement>().ToList();
            if (children.Count <= 0) { return base.MeasureOverride(constraint); }

            double right = 0.0;
            double bottom = 0.0;
            foreach (FrameworkElement child in children)
            {
                Point p = child.TranslatePoint(new Point(child.ActualWidth, child.ActualHeight), this);
                right = Math.Max(right, p.X);
                bottom = Math.Max(bottom, p.Y);
            }

            if (right <= 0.0 || bottom <= 0.0) { return base.MeasureOverride(constraint); }

            return new Size(right, bottom);
        }



        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            this.InvalidateMeasure();
            base.OnRenderSizeChanged(sizeInfo);
        }
    }
}



위 코드에서 DesignMode 일때 건너뛴 이유는, 이렇게 안하면 Designer 에서 오류가 발생합니다. 이미 해당 type 에 대해서 metadata 가 선언되었다는 오류입니다. Canvas.LeftProperty 등은 Dependency Property 가 아니라, Attached Property 입니다. 그렇기 때문에 첫번째 파라미터의 type 이 ExpandingCanvas 면 동작하지 않습니다. 여기 달라붙는 Child 들의 type 을 적어주어야 하기 때문이죠. 그래서 UIElement 의 타입을 넣어주어야 하는데, 그러면 위 이유로 오류가 납니다. -_-; 물론 실행시에는 잘 동작합니다. 해결 방법을 모르겠네요.

디자이너에서도 오류없이 잘 보이고, 실행도 잘 되는 방법을 말이죠.


아시는분은 답변 부탁드립니다 :)




    [ExpandingCanvasTest.xaml]



<Window x:Class="TestMain.ExpandingCanvasTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sw="http://wpf.sungwook.kim/common"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="ExpandingCanvasTest" Height="300" Width="300">
    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <sw:ExpandingCanvas x:Name="PART_ExpandingCanvas">
            <Grid Background="Yellow" Width="100" Height="100">
                <i:Interaction.Behaviors>
                    <sw:DragOnCanvasBehavior BaseElement="{Binding ElementName=PART_ExpandingCanvas}"></sw:DragOnCanvasBehavior>
                </i:Interaction.Behaviors>
            </Grid>
        </sw:ExpandingCanvas>
    </ScrollViewer>
</Window>




위에서 사용한 DragOnCanvasBehavior 는 이전 포스팅을 참고하시기 바랍니다.






'Microsoft > WPF' 카테고리의 다른 글

Expanding Canvas 만들기 2  (0) 2014.11.16
격자 배경 그리기  (0) 2014.11.14
Drag Canvas 만들기  (0) 2014.11.13
TreeView Template 을 통한 Canvas 만들기  (1) 2014.11.12
MEF(Managed Extensibility Framework)  (2) 2014.05.19