Saturday, February 07, 2009

WPF ASCII grid part 2

This is the second in a series where I try to learn how to develop WPF apps. The first part is here.

So my next step was to put all the ASCII grid code into a custom control. My first attempt was to inherit from Grid and add the logic to create the cells in the inherited class. The problem with this approach was that the grid’s row and column definitions could be edited in Visual Studio which I didn’t want. So I decided to inherit from Panel and add the grid as a child control. When I did that, running the application showed up nothing at all. I finally realised that was the wrong approach, since the Panel class is designed to allow the end user to add their own child controls. I guess the WPF runtime is looking at the XAML to see which children should be added to the panel and there aren’t any. So finally I inherited from Control and then had to figure out how to add the grid as a child. Control doesn’t have a Children property so it’s not obvious how to add your own child controls. But it turns out it’s pretty easy to do. You just have to override VisualChildrenCount and GetVisualChild as shown below.

  public class AsciiGrid : Control
  {
    static AsciiGrid()
    {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(AsciiGrid), new FrameworkPropertyMetadata(typeof(AsciiGrid)));
    }

    private Grid grid;
    public AsciiGrid() : base()
    {
      grid = new Grid();

      const int width = 16;
      const int height = 16;
      for (int x = 0; x < width; x++)
      {
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), });
      }
      for (int y = 0; y < height; y++)
      {
        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), });
      }
      for (int x = 0; x < width; x++)
      {
        for (int y = 0; y < height / 2; y++)
        {
          int asciiValue = ((y * width) + (x + 1));

          // number
          TextBlock rect = new TextBlock();
          rect.Text = asciiValue.ToString();
          rect.SetValue(Grid.RowProperty, y * 2);
          rect.SetValue(Grid.ColumnProperty, x);
          grid.Children.Add(rect);

          // ASCII value
          rect = new TextBlock();
          rect.Text = Convert.ToChar(asciiValue).ToString();
          rect.SetValue(Grid.RowProperty, (y * 2) + 1);
          rect.SetValue(Grid.ColumnProperty, x);
          grid.Children.Add(rect);
        }
      }
    }

    protected override int VisualChildrenCount
    {
      get
      {
        return 1;
      }
    }

    protected override Visual GetVisualChild(int index)
    {
      return grid;
    }
  }
So I now have a standalone control in my application. The application looks exactly as it did before but I’ve now got an exceedingly useful re-usable ASCII grid control. OK, maybe not so useful, but this is a learning experience, so cut me some slack. The only thing to note is the changes required to the XAML to host the control, which looks like this.
<Window x:Class="Ascii.AsciiWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Ascii="clr-namespace:Ascii"    
    Title="Ascii" Height="370" Width="413" Loaded="Window_Loaded">
    <Ascii:AsciiGrid x:Name="m_Grid">
    </Ascii:AsciiGrid>
</Window>

No comments: