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!

Comments (7) -

chowkabazaar
4/10/2016 5:17:50 AM #

Hii bro your giving such a valuble information.<a href="http://www.chowkabazaar.com/"; rel="dofollow" target="_blank">chowka bazaar</a> i like this article very much bro. thank you sharing this great information.

Salons in Malviya Nagar
5/4/2016 9:04:57 AM #

Cool post! Thanks for the source code.

olejek do włosów
1/18/2017 8:54:31 PM #

This site really has all the information I wanted concerning this subject and didn't know who to ask.

Click Aquí
3/9/2017 4:07:11 PM #

 Me gusta el video-game a pesar de que puedo intentar ser un poco bastante mas prolongado  Hecha un vistazo y puedes visita mi blog post:  Click Aquí - dotsecret.com/.../netsoltrademark.php

http://chicagotilestores.info/
3/21/2017 12:12:36 AM #

It's especially hard if you believe like you're the only teacher with your school to try fitting new technologies into the planning and classroom delivery.  And thanks to some retrofit technology that's on the way, it's effectively yesterday. But Apple features its own idea about how you can watch video, and contains absolutely nothing to do with standards that anybody else creates.

voyance gratuite par mail
3/23/2017 10:23:43 AM #

Your site helped me a lot to know several things, thank you very much.

voyance gratuite par mail
3/23/2017 10:24:09 AM #

Your site helped me a lot to know several things, thank you very much.

eecoslo.org
3/29/2017 6:33:10 PM #

Direct the scene returning to your site so they can share their thoughts about your statement. One other thing that you'd like to think about is limiting access on the network, notably if you embark on any kind of secure printing or if you've got aspects of the network that are for secure files. But Apple possesses his own idea about how you can watch video, and possesses nothing to do with standards that other people creates.

Find Cheap Solar
3/31/2017 9:39:10 AM #

While it isn't mandatory for manufacturers that will put these labels on the windows, the people do label and score well work most effectively bets. There a multitude of people who neglect to insulate their garage, so don't feel below par if you fall into this category. There are a few components that go into to be able to generate the electricity.  Here is my web page -  Find Cheap Solar - http://Youplaisir.com//blog/922091

pain in back
3/31/2017 10:03:51 AM #

You will also be making an effort to avoid becoming one of many eighty percent of Americans who suffer from back pain. Alternative treatments - Acupuncture Acupuncture is one of many lumbar pain solutions sought by many because not only does it relieve the pain sensation, it improves blood circulation.  He developed the Emotion Code, a simple and powerful way of finding and releasing these trapped energies.  my page ...  pain in back - sosial.sman2bondowoso.sch.id/.../15625

Lets Animate 3 discount coupon
3/31/2017 3:33:07 PM #

Live events can provide long-lasting value once you make them section of your video marketing strategy.

Fortunately in today's society it's now acceptable that individuals will likely change careers and jobs a few times before they finally find something that they revel in, something that they are great at and therefore are thrilled to appear to 5 days a week.  And thanks to some retrofit technology that's in route, it's effectively yesterday.  Silicon is the most common of these materials accustomed to generate electrical current if it's encountered with sunlight.

Proline.Physics.Iisc.Ernet.in
4/1/2017 5:29:35 AM #

With proper use, you'll be able to leave this off your grocery delivery list for months at a time and not worry about shelf life.  Know your industry inside-out and have in mind the job requirements inside-out.  In this dynamic, customer-company relationships will always be packed with mistrust, tension along with a drive to obtain more for less.  Review my blog post rob grey ( Proline.Physics.Iisc.Ernet.in - Proline.Physics.Iisc.Ernet.in/.../Children_Can_Save_Lives_Too )

the Survivalist
4/1/2017 11:58:08 AM #

Now the very custodians of our own nest egg are themselves bankrupt, bailed out by taxpayer money. Among the various natural calamities that have claimed lives and properties and over many years are earthquakes where people might have used a 72 hour earthquake kit. I personally have focused on the region of infectious diseases since I need to be adequately prepared within a potential pandemic.  Also visit my blog ::  the Survivalist - www.shemalezzz.com/.../...prepper-ZPZRIjiGEEj.html

Wilderness Survival Course
4/1/2017 12:27:37 PM #

The first jump will be the difficult one, but once you're indulges with this hobby, you won't find any issue to jump again and again. Over the past few years, the continent may be worried about the H1N1 virus. As you'll be able to see, business continuity planning is important for your company.  Feel free to visit my blog post;  Wilderness Survival Course - Www.Clardym.com/.../Default.aspx

last longerlast longer In bed
4/1/2017 5:20:17 PM #

When sex gets too boring, it can be greater than a bedroom problem - a monotonous sex life can cause undesirable outcomes including divorce and infidelity.  There are breathing exercises and awareness that will dominate, that help prolong sexual intercourse.  since sex is such a taboo subject no-one ever takes the time to train boys the top solutions to have intercourse and ways to last for very long in bed.  Here is my webpage ::  last longerlast longer In bed - http://directcar.xyz/7466826

Faily Law Solicitors
4/2/2017 5:30:50 AM #

Two months later, John and Sally were each sent final divorce papers inside the mail.  In these classes, the couple learns how to talk with their kids in regards to the divorce and how to support them through the procedure for the divorce. If you or your ex-partner has sole physical custodianship, this does not mean visitation can not be arranged.  My web page  Faily Law Solicitors - http://jimmaruffoscal.flavors.me

child custody information
4/2/2017 7:27:55 AM #

This article will look into the main areas of Alabama divorce, such as the grounds for divorce and also the guidelines useful for the division of property and choosing custody rights.  The same website now offers forms and worksheets to download and finish which consider current calculations and Maryland supporting your children laws to ascertain what exactly financial obligations might be required.  Maybe there are many considerations to unravel for our governments but so long as we keep ignoring this plaque, you may never know the next victim will be your child.  Look into my web-site ...  child custody information - http://tiffanyseoli.flavors.me/

Cheap travel
4/2/2017 8:45:23 AM #

Different countries possess a different constitution, laws, and regulations. The utilization of discounted domestic holiday insurance can be a phenomenon which has happened given that air flights are becoming cheaper and cheaper, especially for domestic flights, and more people are traveling.  This track was constructed to prevent washout on account of rain and to shorten the travel time.  My page -  Cheap travel - confederate-trader.com/item.php?id=9163&mode=1

Leatherman Tool
4/2/2017 4:05:25 PM #

Going to a health club isn't approach to plan for a lengthy backpacking trip. If you want a quality tool that will are an extended time then make sure you stay with the 3 big names in multitools.  For either type, examine the handle in order that it's solid making of the strong material, like high-quality steel.  Have a look at my page ...  Leatherman Tool - makenat.com/.../

Child Custody Strategies
4/3/2017 12:47:05 AM #

Many people are so taken aback on the prospect of a divorce that they can don't have any thought of how to get proper matters.  This kind of mediation can be an option to dealing with a court proceeding.  This is huge-if you weren't married for too long, itrrrs likely that, the judge will delve further into learning that which was acquired prior to marriage, throughout the marriage, and how the funds were handled through the marriage.  My blog post;  Child Custody Strategies - www.angryip.de/.../netsoltrademark.php

BHW
4/15/2017 6:35:09 AM #

Magnificent items from you, man. I've bear in mind your stuff prior to and you are just extremely excellent. I actually like what you have obtained right here, really like what you are stating and the way during which you say it. You are making it entertaining and you still take care of to keep it wise. I cant wait to read much more from you. That is really a wonderful site.

Www.Pragatimahavidyalaya.Ac.In
5/20/2017 7:07:24 PM #

The loan will not care that how bad can be your credit score nonetheless it protects your current repayment capacity and steady way to obtain income.  As long as you are saved to a safe and secure website, you should have no fears of your family information being intercepted.  Car title loan borrowers should be able to sue title lenders and void contracts that violate the law.  my homepage :: Segregated Funds ( Www.Pragatimahavidyalaya.Ac.In - Www.pragatimahavidyalaya.Ac.in/.../ )

ksw 39 betting
5/25/2017 11:19:12 PM #

Greetings! Quick question that's totally off topic. Do you know how to make your site mobile friendly? My blog looks weird when browsing from my iphone 4. I'm trying to find a template or plugin that might be able to correct this problem. If you have any suggestions, please share. Thanks!

I just couldn't depart your site before suggesting that I actually enjoyed the usual info a person supply in your visitors? Is gonna be again incessantly in order to check out new posts

Add comment