dharmatech
dharmatech

Reputation: 9527

Efficiently drawing a two-dimensional grid in WPF

Here's a Windows Forms program which draws a two-dimensional grid of squares that are randomly colored black or red:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Forms_Panel_Random_Squares
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Width = 350;
            Height = 350;

            var panel = new Panel() { Dock = DockStyle.Fill };

            Controls.Add(panel);

            var random = new Random();

            panel.Paint += (sender, e) =>
                {
                    e.Graphics.Clear(Color.Black);

                    for (int i = 0; i < 30; i++)
                        for (int j = 0; j < 30; j++)
                        {
                            if (random.Next(2) == 1)
                                e.Graphics.FillRectangle(
                                    new SolidBrush(Color.Red),
                                    i * 10,
                                    j * 10,
                                    10,
                                    10);
                        }
                };
        }
    }
}

The resulting program looks something like this:

enter image description here

Here's a (naive) translation to WPF using Rectangle objects for each square:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WPF_Canvas_Random_Squares
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Width = 350;
            Height = 350;

            var canvas = new Canvas();

            Content = canvas;

            Random random = new Random();

            Rectangle[,] rectangles = new Rectangle[30, 30];

            for (int i = 0; i < rectangles.GetLength(0); i++)
                for (int j = 0; j < rectangles.GetLength(1); j++)
                {
                    rectangles[i, j] =
                        new Rectangle()
                        {
                            Width = 10,
                            Height = 10,
                            Fill = random.Next(2) == 0 ? Brushes.Black : Brushes.Red,
                            RenderTransform = new TranslateTransform(i * 10, j * 10)
                        };

                    canvas.Children.Add(rectangles[i, j]);
                }
        }
    }
}

The WPF version seems to be way more memory inefficient due to the fact that each cell in the world has the overhead of a Rectangle object.

Is there a way to write this program in a style that's as efficient as the Forms version? Or is there no way around creating all those Rectangle objects?

Upvotes: 2

Views: 2453

Answers (2)

dharmatech
dharmatech

Reputation: 9527

Here's a pure WPF solution. FrameworkElement is subclassed. This new subclass (DrawingVisualElement) exposes a DrawingVisual object which can be used to draw.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace DrawingVisualSample
{
    public class DrawingVisualElement : FrameworkElement
    {
        private VisualCollection _children;

        public DrawingVisual drawingVisual;

        public DrawingVisualElement()
        {
            _children = new VisualCollection(this);

            drawingVisual = new DrawingVisual();
            _children.Add(drawingVisual);
        }

        protected override int VisualChildrenCount
        { 
            get { return _children.Count; } 
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= _children.Count)
                throw new ArgumentOutOfRangeException();

            return _children[index];
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Width = 350;
            Height = 350;

            var stackPanel = new StackPanel();

            Content = stackPanel;

            var drawingVisualElement = new DrawingVisualElement();

            stackPanel.Children.Add(drawingVisualElement);

            var drawingContext = drawingVisualElement.drawingVisual.RenderOpen();

            var random = new Random();

            for (int i = 0; i < 30; i++)
                for (int j = 0; j < 30; j++)    
                    drawingContext.DrawRectangle(
                        random.Next(2) == 0 ? Brushes.Black : Brushes.Red,
                        (Pen)null,
                        new Rect(i * 10, j * 10, 10, 10));

            drawingContext.Close();
        }
    }    
}

Upvotes: 2

dharmatech
dharmatech

Reputation: 9527

It is possible to mix WPF and Forms. So instead of going the pure Forms route, the Panel can be embedded into the WPF Window via WindowsFormsHost. Here's a WPF program which demonstrates this:

using System;
using System.Windows;
using System.Windows.Forms.Integration;
using System.Drawing;

namespace WindowsFormsHost_Random_Squares
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Width = 350;
            Height = 350;

            Random random = new Random();

            var windowsFormsHost = new WindowsFormsHost();

            Content = windowsFormsHost;

            var panel = new System.Windows.Forms.Panel()
            { Dock = System.Windows.Forms.DockStyle.Fill };

            windowsFormsHost.Child = panel;

            panel.Paint += (sender, e) =>
                {
                    e.Graphics.Clear(System.Drawing.Color.Black);

                    for (int i = 0; i < 30; i++)
                        for (int j = 0; j < 30; j++)
                        {
                            if (random.Next(2) == 1)
                                e.Graphics.FillRectangle(
                                    new SolidBrush(System.Drawing.Color.Red),
                                    i * 10,
                                    j * 10,
                                    10,
                                    10);
                        }
                };
        }
    }
}

Upvotes: 0

Related Questions