격자 배경 그리기




목차


Canvas 를 사용할때, 간혹 배경을 모눈종이처럼 격자로 할 경우가 생깁니다. 손쉽게는 격자 패턴을 이미지로 만들어서 쓸 수 있지요. 하지만 이때 생기는 문제가 조금 있습니다. 만약 이 Canvas 를 Zoom 한다고 가정해 보죠. 선의 두깨는 항상 같으면 좋을듯 합니다. 모눈의 크기는 Zoom 에 따라 커지고 작아지는데 반하여, 선의 두깨는 같아야 한다는 이야깁니다.


보통 3D 를 다루는 툴의 컴퍼넌트들을 보면, 이런 방식입니다. Unity 나 3D Max 같은 툴들 말이죠.

이 경우, 격자 패턴을 배경으로 깔아 쓰는 방식은 적절하지 못합니다.




개요


FlottingGrid 라는 이름으로 격자가 들어간 Grid 를 만들어 보도록 하겠습니다.

코드의 핵심은 OnRender 를 override 하는 것입니다. 더불어 격자의 선 두깨, 선 색 등의 정보를 위해서 Property 를 몇개 추가하였습니다. Zoom 에 따른 변화들은 다음 포스팅에서 하도록 하고, 이번 포스팅에서는 격자만 그리도록 하겠습니다. 사실 아래 코드만 보고 나시면, Zoom 관련된 부분들은 직접 하실 수 있을거라고 생각됩니다. :)


아래 코드는 Zoom 과 Offset 등.. 기능을 추가하다가 중지한 것이라,, 관련 코드가 좀 섞여 있습니다 -_-;

양해 바랍니다.









    [FlottingGrid]



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.Media;


namespace SW.Common.Controls
{
    public class FlottingGrid : Grid
    {
        public double ZoomRate
        {
            get { return (double) GetValue(ZoomRateProperty); }
            set { SetValue(ZoomRateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomRate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomRateProperty =
            DependencyProperty.Register("ZoomRate", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));


        public int CellCount
        {
            get { return (int) GetValue(CellCountProperty); }
            set { SetValue(CellCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellCountProperty =
            DependencyProperty.Register("CellCount", typeof(int), typeof(FlottingGrid), new FrameworkPropertyMetadata(8, FrameworkPropertyMetadataOptions.AffectsRender));




        public double CellSize
        {
            get { return (double) GetValue(CellSizeProperty); }
            set { SetValue(CellSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellSizeProperty =
            DependencyProperty.Register("CellSize", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsRender));



        public Brush GridBrush
        {
            get { return (Brush) GetValue(GridBrushProperty); }
            set { SetValue(GridBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridBrushProperty =
            DependencyProperty.Register("GridBrush", typeof(Brush), typeof(FlottingGrid), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromArgb(80, 0, 0, 0)), FrameworkPropertyMetadataOptions.AffectsRender));




        public Brush CellBrush
        {
            get { return (Brush) GetValue(CellBrushProperty); }
            set { SetValue(CellBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellBrushProperty =
            DependencyProperty.Register("CellBrush", typeof(Brush), typeof(FlottingGrid), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromArgb(40, 0, 0, 0)), FrameworkPropertyMetadataOptions.AffectsRender));



        public double GridBorder
        {
            get { return (double) GetValue(GridBorderProperty); }
            set { SetValue(GridBorderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridBorder.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridBorderProperty =
            DependencyProperty.Register("GridBorder", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(2.0, FrameworkPropertyMetadataOptions.AffectsRender));




        public double CellBorder
        {
            get { return (double) GetValue(CellBorderProperty); }
            set { SetValue(CellBorderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellBorder.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellBorderProperty =
            DependencyProperty.Register("CellBorder", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));


        public double GridOffsetX
        {
            get { return (double) GetValue(GridOffsetXProperty); }
            set { SetValue(GridOffsetXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridOffsetXProperty =
            DependencyProperty.Register("GridOffsetX", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));




        public double GridOffsetY
        {
            get { return (double) GetValue(GridOffsetYProperty); }
            set { SetValue(GridOffsetYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridOffsetY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridOffsetYProperty =
            DependencyProperty.Register("GridOffsetY", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));





        public double ZoomCenterX
        {
            get { return (double) GetValue(ZoomCenterXProperty); }
            set { SetValue(ZoomCenterXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomCenterX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomCenterXProperty =
            DependencyProperty.Register("ZoomCenterX", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender));





        public double ZoomCenterY
        {
            get { return (double) GetValue(ZoomCenterYProperty); }
            set { SetValue(ZoomCenterYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomCenterY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomCenterYProperty =
            DependencyProperty.Register("ZoomCenterY", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender));





        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            double translatedZoomRate = this.ZoomRate;
            //if (this.ZoomRate > 2.0) { translatedZoomRate = (this.ZoomRate % 2.0) + 1.0; }
            //else if (this.ZoomRate < 0.5) { translatedZoomRate = (this.ZoomRate % 2.0) + 1.0; }

            double scaledCellSize = this.CellSize * translatedZoomRate;
            double scaledGridSize = scaledCellSize * this.CellCount;
            double templateSize = scaledGridSize + (this.GridBorder / 2);

            double offsetX = (this.ZoomCenterX * this.ActualWidth);
            double offsetY = (this.ZoomCenterY * this.ActualHeight);

            DrawingBrush imageBrush = new DrawingBrush() {TileMode = TileMode.Tile, Viewbox = new Rect(0, 0, templateSize, templateSize), Viewport = new Rect(offsetX + this.GridOffsetX, offsetY + this.GridOffsetY, templateSize, templateSize), ViewportUnits = BrushMappingMode.Absolute, ViewboxUnits = BrushMappingMode.Absolute};
            DrawingGroup drawingGroup = new DrawingGroup();

            Brush cellBrush = this.CellBrush.Clone();
            double opacity = 0.5 + (this.ZoomRate - 1.0);
            if(opacity > 1.0)
            {
                opacity = 1.0;
            }
            if(opacity < 0.1)
            {
                opacity = 0.1;
            }
            cellBrush.Opacity = opacity;

            Pen gridPen = new Pen(this.GridBrush, this.GridBorder);
            Pen cellPen = new Pen(cellBrush, this.CellBorder);

            using(DrawingContext context = drawingGroup.Open())
            {
                context.DrawLine(gridPen, new Point(scaledGridSize, 0), new Point(scaledGridSize, templateSize));
                context.DrawLine(gridPen, new Point(0, scaledGridSize), new Point(templateSize, scaledGridSize));

                for(int c = 1; c < this.CellCount; c++)
                {
                    var offset = (double) ((int) (scaledCellSize * c));
                    context.DrawLine(cellPen, new Point(offset, 0), new Point(offset, templateSize));
                }

                for(int r = 1; r < this.CellCount; r++)
                {
                    var offset = (double) ((int) (scaledCellSize * r));
                    context.DrawLine(cellPen, new Point(0, offset), new Point(templateSize, offset));
                }
            }
            imageBrush.Drawing = drawingGroup;
            dc.DrawRectangle(imageBrush, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
        }
    }
}




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

편집용 Canvas 만들기  (0) 2014.11.16
Expanding Canvas 만들기 2  (0) 2014.11.16
격자 배경 그리기  (0) 2014.11.14
Expanding Canvas 만들기  (2) 2014.11.14
Drag Canvas 만들기  (0) 2014.11.13
TreeView Template 을 통한 Canvas 만들기  (1) 2014.11.12