LegoDraw – Databinding of Composites in WPF

[digg]

My little one, she is 5 yrs old, and she likes to paint, draw and above all she likes to play the Lego blocks. I never think of a better idea than writing a simple application that mimics a little bit of Lego blocks – “LegoDraw”. The application will help a lot in explaining composite design pattern and the data binding of two dimensional(2D) arrays/lists containers. You know what its an instant hit, she is not putting off a moment and keep on playing with these, so called “Lego” blocks, you know what, actually they are mare random colored rectangles :)

So how do we approach it, what are the requirements. The requirements/use cases also came from her, i mean my little one, and here are those, what she wants, very simple:-

  • She wants legos to be drawn, erased and scaled.
  • She can change the background color of her choice.
  • Also most importantly she wants the board size to be adjusted/resized.
  • Save/Capture the image drawn.

Quite simple – right, of course they are simple, the requirements came from a little kid, we the elders make things complex, am I right :) Ok lets go ahead. 
The approach i took to make this application as much reusable as I can. And I am telling you, if you want to write some board game this will give you a great starting point as most of the board games are composed of a Board, which actually composed of rows and columns, and each item in the board is a cell. This is a perfect example of a composite design pattern.

Lets begin with the model of our application. The Model I built is based on “Composite” design pattern. This design pattern is an object oriented way of representing recursive data structure, like trees or parent-child relationship. Here is the class model for our application “LegoDraw”

LegoDraw2 Figure-1

All the the Model classes are conformed to the “IBoardWidget” interface and  “IVisitor” interface, and “INotifyPropertyChanged” except the utility classes and UI classes. So what does it mean?? It means that I left the application open for extension using the Visitor Design Pattern and underneath applied a form of Composite Design Pattern. Applying contract of “INotifyPropertyChanged” means that all of these classes are databound ready for two way binding mode. We’ll look at the code one by one and here is how “GridWidget”, the main container class, that is responsible of creating “CellRowWidget” and save in an “ObservableCollection<CellRowWidget>”, while exposing it  with property “GridRows”. This class also has an “indexer” that provides you a convenience of reaching to any cell at [row, col]. You build the “GridWidget” using a static builder method, “BuildGridWidget”, lines 7-12.

   1:  namespace Shams.Wpf.LegoDraw.Model
   2:  {
   3:      [Serializable]
   4:      public class GridWidget : WidgetBase, INotifyPropertyChanged
   5:      {
   6:          // Factory/Builder method
   7:          public static GridWidget BuildGridWidget(int rows, int columns, Brush brush)
   8:          {
   9:              GridWidget grid = new GridWidget(rows, columns, brush);
  10:              grid.CreateRows();
  11:              return grid;
  12:          }
  13:   
  14:          public static GridWidget CopyNew(GridWidget gridOld, int rows, int columns, Brush brush)
  15:          {
  16:              GridWidget grid = null;
  17:              if (gridOld.GridRows.Count > 0)
  18:              {
  19:                  // create a fresh grid
  20:                  grid = GridWidget.BuildGridWidget(rows, columns, brush);
  21:   
  22:                  // copy from the old ones to new one.
  23:                  int rowsCount = grid.RowsCount;
  24:                  int colsCount = grid.ColumnsCount;
  25:   
  26:                  if (gridOld.Size < grid.Size)
  27:                  {
  28:                      rowsCount = gridOld.RowsCount;
  29:                      colsCount = gridOld.ColumnsCount;
  30:                  }
  31:   
  32:                  for (int i = 0; i < rowsCount; ++i)
  33:                  {
  34:                      for (int j = 0; j < colsCount; ++j)
  35:                      {
  36:                          grid[i, j].CellId = gridOld[i, j].CellId;
  37:                          grid[i, j].Background = gridOld[i, j].Background;
  38:                          grid[i, j].BorderBrush = gridOld[i, j].BorderBrush;
  39:                          grid[i, j].IsCellVisited = gridOld[i, j].IsCellVisited;
  40:                      }
  41:                  }
  42:              }
  43:              return grid;
  44:          }
  45:   
  46:          #region INotifyPropertyChanged Members
  47:   
  48:          public event PropertyChangedEventHandler PropertyChanged;
  49:   
  50:          #endregion
  51:   
  52:          #region ctors
  53:   
  54:          public int RowsCount { get; set; }
  55:          public int ColumnsCount { get; set; }
  56:          public int Size { get; private set; }
  57:          public Brush RowBrush { get; private set; }
  58:   
  59:          public GridWidget()
  60:              : this(16, Brushes.Transparent)  // default is 16 x 16
  61:          {
  62:   
  63:          }
  64:   
  65:          public GridWidget(int size, Brush brush)
  66:              : this(size, size, brush)   // denotes square matrix
  67:          {
  68:          }
  69:   
  70:          public ObservableCollection<CellRowWidget> GridRows { get; private set; }
  71:   
  72:          public GridWidget(int rowSize, int colSize, Brush brush)
  73:          {
  74:              this.Tag = this;
  75:              this.RowsCount = rowSize;
  76:              this.ColumnsCount = colSize;
  77:              this.Size = rowSize * colSize;
  78:              this.RowBrush = brush;
  79:          }
  80:   
  81:          public void CreateRows()
  82:          {
  83:              try
  84:              {
  85:   
  86:                  this.GridRows = new ObservableCollection<CellRowWidget>();
  87:                  this.GridRows.CollectionChanged += 
                             new NotifyCollectionChangedEventHandler(GridRows_CollectionChanged);
  88:   
  89:                  for (int rowIndex = 0; rowIndex < RowsCount; ++rowIndex)
  90:                  {
  91:                      CellRowWidget row = 
  92:                          CellRowWidget.BuildCellRowWidget(this, 
  93:                          rowIndex, 
  94:                          ColumnsCount, 
  95:                          this.RowBrush, 
  96:                          (rowIndex + 1 == RowsCount));
  97:   
  98:                      row.PropertyChanged += new PropertyChangedEventHandler(Row_PropertyChanged);
  99:                      GridRows.Add(row);
 100:                  }
 101:              }
 102:              catch (Exception ex)
 103:              {
 104:                  System.Diagnostics.Trace.WriteLine(ex.ToString());
 105:              }
 106:          }
 107:   
 108:   
 109:          void Row_PropertyChanged(object sender, PropertyChangedEventArgs e)
 110:          {
 111:              CellRowWidget row = sender as CellRowWidget;
 112:          }
 113:   
 114:          void GridRows_CollectionChanged(object sender, 
                          System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
 115:          {
 116:              System.Diagnostics.Debug.WriteLine(sender.ToString() + "|" + e.Action);
 117:          }
 118:   
 119:          #endregion
 120:   
 121:   
 122:          /// <summary>
 123:          /// Indexer, for the cell @ row and col
 124:          /// </summary>
 125:          /// <param name="row"></param>
 126:          /// <param name="col"></param>
 127:          /// <returns></returns>
 128:          public CellWidget this[int row, int col]
 129:          {
 130:              get
 131:              {
 132:                  // Bounds checking
 133:                  if (row < 0 || row >= GridRows.Count)
 134:                  {
 135:                      throw new ArgumentOutOfRangeException("row", row, "Invalid Row Index");
 136:                  }
 137:                  return GridRows[row][col];
 138:              }
 139:          }
 140:   
 141:          public override void OnPropertyChanged(string propertyName)
 142:          {
 143:              if (PropertyChanged != null)
 144:              {
 145:                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 146:              }
 147:          }
 148:   
 149:          public override void Accept(IVisitor visitor)
 150:          {
 151:              visitor.Visit(this);
 152:          }
 153:      }
 154:  }

Here is the code for the “CellRowWidget”. It is also a container class and maintains the a collection of cells in a row using “ObservableCollection<CellWidget>”, and exposes it using a property named “CellsRow”.  You can also create it  using a builder method  “BuildCellRowWidget” as shown in the lines 9-18.

   1:  namespace Shams.Wpf.LegoDraw.Model
   2:  {
   3:      [Serializable]
   4:      public class CellRowWidget : WidgetBase, INotifyPropertyChanged
   5:      {
   6:          private static readonly Random randomNumber = new Random((int)DateTime.Now.Ticks);
   7:          
   8:          // Factory/Builder method
   9:          public static CellRowWidget BuildCellRowWidget(IBoardWidget parent,
  10:              int rowIndex,
  11:              int size,
  12:              Brush brush,
  13:              bool isLastRow)
  14:          {
  15:              CellRowWidget cellsRow = new CellRowWidget(parent, rowIndex, size, brush, isLastRow);
  16:              cellsRow.CreateCells();
  17:              return cellsRow;
  18:          }
  19:   
  20:          #region INotifyPropertyChanged Members
  21:   
  22:          public event PropertyChangedEventHandler PropertyChanged;
  23:   
  24:          #endregion
  25:   
  26:          // A grid row is a composition of grid-cells
  27:          public ObservableCollection<CellWidget> CellsRow { get; private set; }
  28:          public int CellsCount { get; set; }
  29:          public Brush CellsBrush { get; private set; }
  30:   
  31:          public CellRowWidget(IBoardWidget parent, 
  32:              int rowIndex, 
  33:              int size,
  34:              Brush brush,
  35:              bool isLastRow)
  36:          {
  37:              this.CellsCount = size;
  38:              this.Tag = this;
  39:              this.Parent = parent;
  40:              this.RowIndex = rowIndex;
  41:              this.CellsBrush = brush;
  42:   
  43:          }
  44:          
  45:          public void CreateCells()
  46:          {
  47:              try
  48:              {
  49:                  this.CellsRow = new ObservableCollection<CellWidget>();
  50:                  for (int columnIndex = 0; columnIndex < CellsCount; columnIndex++)
  51:                  {
  52:                      CellWidget cell = new CellWidget(this, rowIndex, columnIndex);
  53:                      cell.Background = this.CellsBrush;
  54:                      CellsRow.Add(cell);
  55:                  }
  56:              }
  57:              catch (Exception ex)
  58:              {
  59:                  System.Diagnostics.Trace.WriteLine(ex.ToString());
  60:              }
  61:          }
  62:   
  63:          public CellWidget this[int col]
  64:          {
  65:              get
  66:              {
  67:                  // Bounds checking
  68:                  if (col < 0 || col >= CellsRow.Count)
  69:                  {
  70:                      throw new ArgumentOutOfRangeException("col", col, "Invalid Column Index");
  71:                  }
  72:                  return CellsRow[col];
  73:              }
  74:          }
  75:   
  76:          int rowIndex = -1;
  77:          public int RowIndex
  78:          {
  79:              get
  80:              {
  81:                  return rowIndex;
  82:              }
  83:              set
  84:              {
  85:                  if (rowIndex != value)
  86:                  {
  87:                      rowIndex = value;
  88:                      OnPropertyChanged("RowIndex");
  89:                  }
  90:              }
  91:          }
  92:   
  93:          public override void OnPropertyChanged(string propertyName)
  94:          {
  95:              if (PropertyChanged != null)
  96:              {
  97:                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  98:              }
  99:          }
 100:   
 101:          public override void Accept(IVisitor visitor)
 102:          {
 103:              visitor.Visit(this);
 104:          }
 105:      }
 106:  }

Finally the leaf class “CellWidget”, this is the one that takes the form of “Border” UI during run time because of the “DataTemplate” defined in the user interface. Here is the code for it.

   1:  namespace Shams.Wpf.LegoDraw.Model
   2:  {
   3:      [Serializable]
   4:      public class CellWidget : WidgetBase, INotifyPropertyChanged
   5:      {
   6:          #region INotifyPropertyChanged Members
   7:   
   8:          public event PropertyChangedEventHandler PropertyChanged;
   9:   
  10:          #endregion
  11:   
  12:          public CellWidget()
  13:              : this(null)
  14:          {
  15:          }
  16:          public CellWidget(IBoardWidget parent)
  17:              : this(parent, null)
  18:          {
  19:   
  20:          }
  21:   
  22:          public CellWidget(IBoardWidget parent, int? cellId)
  23:              : this(parent, cellId, 0, 0)
  24:          {
  25:   
  26:          }
  27:          public CellWidget(IBoardWidget parent, int cellRowIndex, int cellColIndex)
  28:              : this(parent, null, cellRowIndex, cellColIndex)
  29:          {
  30:          }
  31:   
  32:          public CellWidget(IBoardWidget parent, int? cellId, int cellRowIndex, int cellColIndex)
  33:              : this(parent, cellId, cellRowIndex, cellColIndex, null)
  34:          {
  35:          }
  36:   
  37:   
  38:          public CellWidget(IBoardWidget parent, int? cellId, int cellRowIndex, int cellColIndex, object cellTag)
  39:          {
  40:              try
  41:              {
  42:                  this.parent = parent;
  43:                  this.cellId = cellId;
  44:                  this.cellRowIndex = cellRowIndex;
  45:                  this.cellColumnIndex = cellColIndex;
  46:                  this.tag = cellTag;
  47:              }
  48:              catch (Exception ex)
  49:              {
  50:                  System.Diagnostics.Trace.WriteLine(ex.ToString());
  51:              }
  52:          }
  53:   
  54:          int? cellId = null;
  55:          public int? CellId
  56:          {
  57:              get
  58:              {
  59:                  return cellId;
  60:              }
  61:              set
  62:              {
  63:                  if (cellId != value)
  64:                  {
  65:                      cellId = value;
  66:                      OnPropertyChanged("CellId");
  67:                  }
  68:              }
  69:          }
  70:   
  71:          int cellRowIndex = -1;
  72:          public int CellRowIndex
  73:          {
  74:              get
  75:              {
  76:                  return cellRowIndex;
  77:              }
  78:              set
  79:              {
  80:                  if (cellRowIndex != value)
  81:                  {
  82:                      cellRowIndex = value;
  83:                      OnPropertyChanged("CellRowIndex");
  84:                  }
  85:              }
  86:          }
  87:   
  88:          int cellColumnIndex = -1;
  89:          public int CellColumnIndex
  90:          {
  91:              get
  92:              {
  93:                  return cellColumnIndex;
  94:              }
  95:              set
  96:              {
  97:                  if (cellColumnIndex != value)
  98:                  {
  99:                      cellColumnIndex = value;
 100:                      OnPropertyChanged("CellColumnIndex");
 101:                  }
 102:              }
 103:          }
 104:   
 105:          bool isCellVisited = false;
 106:          public bool IsCellVisited
 107:          {
 108:              get
 109:              {
 110:                  return isCellVisited;
 111:              }
 112:              set
 113:              {
 114:                  if (isCellVisited != value)
 115:                  {
 116:                      isCellVisited = value;
 117:                      OnPropertyChanged("IsCellVisited");
 118:                  }
 119:              }
 120:          }
 121:   
 122:          Brush background = Brushes.Transparent;
 123:          public Brush Background 
 124:          {
 125:              get
 126:              {
 127:                  return background;
 128:              }
 129:              set
 130:              {
 131:                  if (background != value)
 132:                  {
 133:                      background = value;
 134:                      OnPropertyChanged("Background");
 135:                  }
 136:              }
 137:          }
 138:   
 139:          Brush borderBrush = Brushes.Transparent;
 140:          public Brush BorderBrush
 141:          {
 142:              get
 143:              {
 144:                  return borderBrush;
 145:              }
 146:              set
 147:              {
 148:                  if (borderBrush != value)
 149:                  {
 150:                      borderBrush = value;
 151:                      OnPropertyChanged("BorderBrush");
 152:                  }
 153:              }
 154:          }
 155:   
 156:          public override void OnPropertyChanged(string propertyName)
 157:          {
 158:              if (PropertyChanged != null)
 159:              {
 160:                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 161:              }
 162:          }
 163:   
 164:          public override void Accept(IVisitor visitor)
 165:          {
 166:              visitor.Visit(this);
 167:          }
 168:      }
 169:  }

The famous “LegoDrawBoard” user control is created. Here is the XAML for it.

   1:  <UserControl x:Class="Shams.Wpf.LegoDraw.Controls.LegoDrawBoard"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:local="clr-namespace:Shams.Wpf.LegoDraw.Controls"
   5:      xmlns:model="clr-namespace:Shams.Wpf.LegoDraw.Model"         
   6:      HorizontalAlignment ="Stretch"
   7:      HorizontalContentAlignment ="Stretch"
   8:      VerticalAlignment ="Stretch"
   9:      VerticalContentAlignment ="Stretch"
  10:      local:LegoDrawBoard.TotalColors="9"
  11:      x:Name="This">
  12:   
  13:      <UserControl.Resources>
  14:          <ResourceDictionary>
  15:              <DataTemplate DataType="{x:Type model:CellWidget}">
  16:                  <Grid Width="Auto" Height="Auto">
  17:                      <Border x:Name="PART_Border" 
  18:                              BorderThickness="4" 
  19:                              Background="{Binding Path=Background}"
  20:                              BorderBrush="{Binding Path=BorderBrush}" 
  21:                              
  22:                              MouseLeftButtonDown="PART_Border_MouseLeftButtonDown"
  23:                              MouseMove="PART_Border_MouseMove"
  24:                              MouseLeftButtonUp="PART_Border_MouseLeftButtonUp" />
  25:                  </Grid>
  26:                  <DataTemplate.Triggers>
  27:                      <DataTrigger Binding="{Binding Path=CellId}" Value="{x:Null}">
  28:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Transparent" />
  29:                      </DataTrigger>
  30:                      <DataTrigger Binding="{Binding Path=CellId}" Value="0">
  31:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Yellow" />
  32:                      </DataTrigger>
  33:                      <DataTrigger Binding="{Binding Path=CellId}" Value="1">
  34:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Cyan" />
  35:                      </DataTrigger>
  36:                      <DataTrigger Binding="{Binding Path=CellId}" Value="2">
  37:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Red" />
  38:                      </DataTrigger>
  39:                      <DataTrigger Binding="{Binding Path=CellId}" Value="3">
  40:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Blue" />
  41:                      </DataTrigger>
  42:                      <DataTrigger Binding="{Binding Path=CellId}" Value="4">
  43:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Purple" />
  44:                      </DataTrigger>
  45:                      <DataTrigger Binding="{Binding Path=CellId}" Value="5">
  46:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="SpringGreen" />
  47:                      </DataTrigger>
  48:                      <DataTrigger Binding="{Binding Path=CellId}" Value="6">
  49:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Pink" />
  50:                      </DataTrigger>
  51:                      <DataTrigger Binding="{Binding Path=CellId}" Value="7">
  52:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="Orange" />
  53:                      </DataTrigger>
  54:                      <DataTrigger Binding="{Binding Path=CellId}" Value="8">
  55:                          <Setter TargetName="PART_Border" Property="BorderBrush" Value="SteelBlue" />
  56:                      </DataTrigger>
  57:                  </DataTemplate.Triggers>
  58:              </DataTemplate>
  59:   
  60:              <DataTemplate x:Key ="GridRowTemplate">
  61:                  <ItemsControl ItemsSource ="{Binding Path=CellsRow}">
  62:                      <ItemsControl.ItemsPanel>
  63:                          <ItemsPanelTemplate>
  64:                              <UniformGrid Columns ="1"/>
  65:                          </ItemsPanelTemplate>
  66:                      </ItemsControl.ItemsPanel>
  67:                  </ItemsControl>
  68:              </DataTemplate>
  69:          </ResourceDictionary>
  70:      </UserControl.Resources>
  71:   
  72:      <Grid x:Name="MainGrid" >
  73:          <Grid.DataContext>
  74:              <Binding ElementName="This" Path="MainGridWidget" />
  75:          </Grid.DataContext>
  76:          <Border BorderBrush="Silver" BorderThickness="2" >
  77:              <ItemsControl x:Name ="LegoDrawItemsControl" 
  78:                            ItemTemplate ="{StaticResource GridRowTemplate}" 
  79:                            ItemsSource ="{Binding Path=GridRows}" >
  80:                  <ItemsControl.ItemsPanel>
  81:                      <ItemsPanelTemplate>
  82:                          <UniformGrid Rows ="1"/>
  83:                      </ItemsPanelTemplate>
  84:                  </ItemsControl.ItemsPanel>
  85:              </ItemsControl>
  86:          </Border>
  87:      </Grid>
  88:  </UserControl>

Most of the magic is done in the user control, XAML of which is shown above. Most important is the CellWidget, that represents the cell of any BoardGame, like ours LegoDrawBoard. Lets take a closer look at lines 15-58. The CellWidget class that is defined in our code behind under model and it will be rendered as the DataTemplate. So CellWidget will assume the look and feel of a Border and bounded its properties like Background and BorderBrush with the CellWidget’s respective properties. So what does it mean. It means that during run-time if the CellWidget’s BorderBrush or Background changes, will reflect those changes in the Border. So what are those triggers for lines 26-56. There is another property that we will be monitoring is the CellId of the CellWidget, that when changes will change the border color, thats how we created the randomness of those cell colors. You can give any template here, for further styling of user interface instead of Border or so.

The DataContext is set to another class that is “GridWidget” that actually mimics the 2D board. In Lines 60-68, we created a DataTemplate that actually represents GridWidget.GridRows and those GridRows are composed of CellsRow that represents a row of cells, of type CellWidget. Since CellsRow is a collection of Cells, it should be bounded to some UI container. Most of the containers, like Lists, ListViews and Comboboxes and  are of ItemsControl. So I selected the ItemsControl for our purpose. Its light weight and simple to use. So it represents each CellsRow UniformGrid of column. And Our GridWidget is actually composed of GridRows, So we used another ItemsControl (lines 77-85) and bounded it to GridRows, where these rows are defined as UniformGrid of Rows. Now we have the GridWidget Board ready. Here is the code for it:

And also one more the code behind of the User Control itself, the “LegoDrawBoard”. Most of the Mouse related actions are taken place here. So when the mouse move (lines 153- 179) is happening, we actually are drawing the cell by randomizing its property “CellID”, that actually trigger’s events in the “DataTemplate” defined in the User Control – LegoDrawBoard. One of my other favorite is the Screen Capture, “CaptureAsImageSource” (lines 53-86) below. Also See “Ctrl+Mouse” & “Shift+Mouse” for some more actions while drawing Logos.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Imaging;
  12:  using System.Windows.Navigation;
  13:  using System.Windows.Shapes;
  14:  using Shams.Wpf.LegoDraw.Model;
  15:  using System.IO;
  16:  using System.Threading;
  17:  using Shams.Wpf.LegoDraw.Utils;
  18:   
  19:  namespace Shams.Wpf.LegoDraw.Controls
  20:  {
  21:      /// <summary>
  22:      /// Interaction logic for LegoDrawBoard.xaml
  23:      /// </summary>
  24:      public partial class LegoDrawBoard : UserControl
  25:      {
  26:          #region statics
  27:   
  28:          public static readonly DependencyProperty TotalColorsProperty = DependencyProperty.RegisterAttached("TotalColors",
  29:              typeof(int), 
  30:              typeof(LegoDrawBoard),
  31:              new PropertyMetadata(7, new PropertyChangedCallback(OnTotalColorsChanged)));
  32:   
  33:          public static int GetTotalColors(DependencyObject element)
  34:          {
  35:              if (element == null)
  36:                  throw new ArgumentNullException("element:TotalColorsProperty");
  37:   
  38:              return (int)element.GetValue(TotalColorsProperty);
  39:          }
  40:   
  41:          public static void SetTotalColors(DependencyObject element, int value)
  42:          {
  43:              if (element == null)
  44:                  throw new ArgumentNullException("element:TotalColorsProperty");
  45:   
  46:              element.SetValue(TotalColorsProperty, value);
  47:          }
  48:   
  49:          private static void OnTotalColorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  50:          {
  51:          }
  52:   
  53:          public static RenderTargetBitmap CaptureAsImageSource(FrameworkElement element)
  54:          {
  55:              // Save current element's transform
  56:              Transform transform = element.LayoutTransform;
  57:              element.LayoutTransform = null;
  58:   
  59:              // setup margin offsets
  60:              Thickness margin = element.Margin;
  61:              element.Margin = new Thickness(0, 0,margin.Right - margin.Left, margin.Bottom - margin.Top);
  62:   
  63:              // Get the size of canvas
  64:              Size size = new Size(element.ActualWidth, element.ActualHeight);
  65:   
  66:              // force control to Update
  67:              element.Measure(size);
  68:              element.Arrange(new Rect(size));
  69:              element.UpdateLayout();
  70:   
  71:              RenderTargetBitmap targetBitmap = new RenderTargetBitmap((int)element.ActualWidth, 
  72:                  (int)element.ActualHeight, 
  73:                  96, 
  74:                  96, 
  75:                  PixelFormats.Pbgra32);
  76:   
  77:              // Now, most importantly render the element
  78:              targetBitmap.Render(element);
  79:   
  80:              // go back to the previous old values.
  81:              element.LayoutTransform = transform;
  82:              element.Margin = margin;
  83:              element.UpdateLayout();
  84:              
  85:              return targetBitmap;
  86:          }
  87:                  
  88:          private static readonly Random randomNumber = new Random((int)DateTime.Now.Ticks);
  89:   
  90:          #endregion
  91:   
  92:          // A grid row is a composition of grid-cells
  93:          public GridWidget MainGridWidget { get; set; }
  94:          public bool DeleteMode { get; set; }
  95:          public Brush CurrentBrush { get; private set; }
  96:   
  97:          private bool isPainting = false;
  98:          private int ColorsCount { get; set; }
  99:   
 100:          public LegoDrawBoard()
 101:          {
 102:              // NewBoard(); // needed for XAML based dataContext, that it should be initialized...
 103:              InitializeComponent();
 104:          }
 105:          public void NewBoard()
 106:          {
 107:              NewBoard(16, Brushes.Transparent);
 108:          }
 109:          public void NewBoard(int rows, Brush brush)
 110:          {
 111:              NewBoard(rows, rows, brush);
 112:          }
 113:   
 114:          public void NewBoard(int rows, int columns, Brush brush)
 115:          {
 116:              this.CurrentBrush = brush;
 117:              this.MainGridWidget = GridWidget.BuildGridWidget(rows, columns, brush);
 118:              this.ColorsCount = LegoDrawBoard.GetTotalColors(this);
 119:              this.MainGrid.DataContext = this.MainGridWidget;
 120:              this.DeleteMode = false;
 121:          }
 122:   
 123:          public void CopyToNewBoard(int rows, Brush brush)
 124:          {
 125:              this.MainGridWidget = GridWidget.CopyNew(this.MainGridWidget, rows, rows, brush);
 126:              this.MainGrid.DataContext = this.MainGridWidget;
 127:          }
 128:   
 129:          public void ChangeBackground(Brush brush)
 130:          {
 131:              this.CurrentBrush = brush;
 132:              for (int i = 0; i < this.MainGridWidget.RowsCount; ++i)
 133:              {
 134:                  for (int j = 0; j < this.MainGridWidget.ColumnsCount; ++j)
 135:                  {
 136:                      CellWidget cell = MainGridWidget[i, j];
 137:                      cell.Background = brush;
 138:                  }
 139:              }
 140:          }
 141:   
 142:          // adapter method...
 143:          public RenderTargetBitmap Capture()
 144:          {
 145:              return LegoDrawBoard.CaptureAsImageSource(this);
 146:          }
 147:   
 148:          private void PART_Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 149:          {
 150:              isPainting = true;
 151:          }
 152:   
 153:          private void PART_Border_MouseMove(object sender, MouseEventArgs e)
 154:          {
 155:              Border border = sender as Border;
 156:              CellWidget cell = border.DataContext as CellWidget;
 157:              
 158:              // Delete Mode
 159:              if (isPainting && 
 160:                  (Keyboard.IsKeyDown(Key.LeftCtrl) || 
 161:                  Keyboard.IsKeyDown(Key.RightCtrl) || 
 162:                  this.DeleteMode))
 163:              {
 164:                  cell.CellId = null;
 165:                  cell.Background = this.CurrentBrush;
 166:              }
 167:              else if (isPainting && 
 168:                  (Keyboard.IsKeyDown(Key.LeftShift) || 
 169:                  Keyboard.IsKeyDown(Key.RightShift)))
 170:              {
 171:                  cell.Background = 
 172:                      BrushesHelper.BrushArray[randomNumber.Next(0, BrushesHelper.BrushArray.Length)];
 173:                  cell.CellId = randomNumber.Next(0, this.ColorsCount);
 174:              }
 175:              else if (isPainting) // painting mode...
 176:              {
 177:                  cell.CellId = randomNumber.Next(0, this.ColorsCount);                                
 178:              }
 179:          }
 180:   
 181:          private void PART_Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
 182:          {
 183:              isPainting = false;
 184:          }
 185:       }
 186:  }

And here is the class model diagram for the UI-Classes:

LegoDraw.UI Figure-2

Refer to my [post1]and [post2] for the XAML for Window, “LegoDrawWin”  and the user control “LegoDrawBoard”. Here is the final output of the intended application, “LegoDraw” (the character inside is drawn by my little one :)

.CapturedImage-Geeks Figure-3

I tried to build a framework for the board-games, and since most of the stuff explained in the post, is done using XAML with focus on “Databinding”, you can customize the application very easily for your needs. If you have questions or need further explanation, contact me.

That's all for now folks, all the “source code” is included in the attached project. Enjoy :)

P.S; One of the captured image is here, send me yours, will publish on this site. See you then!

CapturedImage-Geeks2Figure-4

Download File - LegoDraw Project
If you enjoyed reading this blog, leave your valuable feedback and consider subscribing to the RSS feed. You can also subscribe to it by email. Also, you can follow me on Twitter. Thank you!

Add comment