0

I'm learning WPF and try to create a fully skinnable and configurable backgammon board. The board contains a numbers of pins which are derived from a grid and contain a number of ellipses (the checkers). The pins are placed on another grid 'MainGrid' which is basically the board. The parameters for creating the pins are stored in a resource directory as a string. The string is actually an attached property of the board. (the configuration example only shows the configuration for the two bar pins)

<Style x:Key="MainGrid_style" TargetType="Grid">
    // ...
    <Setter Property="bgb:BgBoard.BarParams" Value="Bottom,5,8,1,1,4/Top,5,8,6,1,4"/>
</Style>

The style is applied to the main grid as follows :

<Grid x:Name="MainGrid" Style="{DynamicResource MainGrid_style}">

My board is coded as follows :

public partial class BgBoard : Window
{
    public static DependencyProperty BarParamsProperty = DependencyProperty.RegisterAttached("BarParams", typeof(string),
          typeof(BgBoard), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None,
          new PropertyChangedCallback(BarParamsPropertyChanged)));
    public static string GetBarParams(Grid Grid) { return Convert.ToString(Grid.GetValue(BarParamsProperty)); }
    public static void SetBarParams(Grid Grid, string Value) { Grid.SetValue(BarParamsProperty, Value); }
    private static void BarParamsPropertyChanged(Object Sender, DependencyPropertyChangedEventArgs e)
    {
        Grid Grid = (Grid)Sender;
        string[] sBarParams = GetBarParams(Grid).Split('/');
        for (int player = 0; player <= 1; player++)
        {
            // *** sBarParams[player] is further parsed and parameters are determined
            BgPin pin = new BgPin(type, size);
            // *** Set the other pin params
            pin.Name = "Bar" + player;
            Grid.Children.Add(pin);
        }
    }

This works perfectly and puts the two bar pins on the board as expected. However, now i want to be able to reference the two pins as an array ( _Bar[0] and _Bar[1] )

So i added the following field to my board :

    private BgPin[] _Bar = new BgPin[2];  

I cannot instantiate or assign these pins via the attached property because the BarParamsPropertyChanged method is static.

So in my Board's constructor i've added :

public BgBoard()
{
    InitializeComponent();
    _Bar[0] = (BgPin)MainGrid.FindName("Bar0");
}

However, this doesn't work as _Bar[0] is still null after this instruction. Any ideas how i could reference the object with name "Bar0" as _Bar[0]? Any solution is ok. It is not required to work via the 'name'-property.

Rohit Vats
  • 79,502
  • 12
  • 161
  • 185

3 Answers3

0

You can try it this way :

  1. create a static event
  2. subscribe a non-static method to that event in the constructor
  3. put logic to add BgPin to array in the subscriber method, This will guarantee the logic only called after BgPins created
  4. in the static method (BarParamsPropertyChanged in this case), call the static event so that the subscriber method get invoked
  5. make the class (BgBoard in this case) implement IDisposable, and in Dispose method implementation unsubscribe the non-static method from the static event. This step is needed to make sure that instance of the class can be properly GC-ed.

For example (not tested) :

public partial class BgBoard : Window, IDisposable
{
    private static event Action myEvent;
    public BgBoard()
    {
        InitializeComponent();
        myEvent += myMethod;
    }

    public void myMethod() 
    { 
        _Bar[0] = (BgPin)MainGrid.FindName("Bar0");
    }

    public void Dispose()
    {
        myEvent -= myMethod;
    }
}

[Reference]

har07
  • 88,338
  • 12
  • 84
  • 137
  • Tried it, but _Bar[0] is still null after the assignment, so there must be some reason why (BgPin)MainGrid.FindName("Bar0") returns null, but i can't figure out what exactly. – user3277372 Feb 09 '14 at 10:43
  • hmm.. just to make sure: 1. you call `myEvent()` at the end of `BarParamsPropertyChanged` method. 2. `Sender` name is `MainGrid`. If both are checked, then I don't have any other idea for the moment. – har07 Feb 09 '14 at 10:50
0

Creating GUI elements in code-behind isn't how WPF was designed to be used. You should instead be creating data models to represent your board and then using data binding to display that data in your view. To see a good example of this have a look at my answer to another SO question about displaying a chess board.

Community
  • 1
  • 1
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • I had a look at your chessboard implementation. The reason i choose to create the backgammon pins in codebehind and not in XAML is that i don't want to write repetitive XAML code for the 28 pins on the backgammon board (2 bars, 24 normal and 2 home). The coding for these 28 pins is exactly the same. The only difference between them is the player and the number of checkers on the pin. – user3277372 Feb 09 '14 at 09:54
  • Also, i'd like the configuration file to be compact. The configuration of the pins is to be defined as a string like : PinParams = "Bottom,5,8,1,1,4/Top,5,8,6,1,4/..." and not like ... – user3277372 Feb 09 '14 at 10:23
  • The repetitive code in my chess example simply mapped chess piece types to a URL, the URL itself could have just as easily been placed in a ChessPiece type in the view model or in a converter class in the View. In any case you wouldn't need to do anything like that yourself because you only have 2 piece types and 2 pin types. The goal of WPF isn't to do everything in XAML, it's to maintain good separation of concerns by keeping your view logic completely outside the view. – Mark Feldman Feb 09 '14 at 19:52
  • I'm still learning how to use WPF properly. I've read about using a viewmodel and completely separating the model and the view. However, i don't really know how to do that for my backgammon board. The problem is that the type and size of the pins in my skins are dynamic. In one skin there can be both bottom and top pins. In another skin, all pins could be bottom pins. Also the number of checkers a pin can hold differs between skins. Its even possible that every pin can have a different size (= max number of checkers it can hold). – user3277372 Feb 12 '14 at 16:45
  • This means that whenever the skin is changed, i have to recreate the pins because their memory requirements change. The only way i could figure out to do this is by recreating the pins in codebehind when the skin changes. – user3277372 Feb 12 '14 at 16:45
0

Found the solution myself. I've added the following code in the static method :

BgBoard Board = (BgBoard)Grid.Parent;
Board._Bar[player] = pin;  

Here's the full coding : XAML of skin file:

<Style x:Key="MainGrid_style" TargetType="Grid">
    <Setter Property="Background">
        <Setter.Value>
            <ImageBrush ImageSource="pack://SiteOfOrigin:,,,/Graphics/WoodBoard.jpg"/> 
        </Setter.Value>
    </Setter>
    <Setter Property="bgb:BgBoard.GridRows" Value="44*,45*,45*,20*,44*,48*,44*,20*,45*,45*,44*"/>
    <Setter Property="bgb:BgBoard.GridColumns" Value="36*,30*,30*,30*,30*,30*,30*,2*,30*,1*,30*,30*,30*
                                                     ,30*,30*,30*,13*,33*,36*,22*,37*,4*,37*,20*,30*"/>
    <Setter Property="bgb:BgBoard.BarParams" Value="Bottom,5,8,1,1,4/Top,5,8,6,1,4"/>
</Style>

XAML BgBoard

<Grid x:Name="MainGrid" Style="{DynamicResource MainGrid_style}">

Codebehind BgBoard

public partial class BgBoard : Window 
{

    public static DependencyProperty BarParamsProperty = DependencyProperty.RegisterAttached("BarParams", typeof(string),
          typeof(BgBoard), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None,
          new PropertyChangedCallback(BarParamsPropertyChanged)));
    public static string GetBarParams(Grid Grid) { return Convert.ToString(Grid.GetValue(BarParamsProperty)); }
    public static void SetBarParams(Grid Grid, string Value) { Grid.SetValue(BarParamsProperty, Value); }
    private static void BarParamsPropertyChanged(Object Sender, DependencyPropertyChangedEventArgs e)
{
        Grid Grid = (Grid)Sender;
        string[] sBarParams = GetBarParams(Grid).Split('/');
        for (int player = (int)Player.Player1; player <= (int)Player.Player2; player++)
        {
            string[] sBarParam = sBarParams[player].Split(',');
            string type = sBarParam[0];
            int size = Convert.ToInt32(sBarParam[1]);
            int column = Convert.ToInt32(sBarParam[2]);
            int row = Convert.ToInt32(sBarParam[3]);
            int columnspan = Convert.ToInt32(sBarParam[4]);
            int rowspan = Convert.ToInt32(sBarParam[5]);
            BgPin pin = new BgPin(type, size);
            Grid.SetColumn(pin, column);
            Grid.SetRow(pin, row);
            Grid.SetColumnSpan(pin, columnspan);
            Grid.SetRowSpan(pin, rowspan);
            pin.Player = (Player)player;
            Grid.Children.Add(pin);
            BgBoard Board = (BgBoard)Grid.Parent;
            Board._Bar[player] = pin;
        }
    }

    private BgPin[] _Bar = new BgPin[2];       

    public BgBoard()
    {
        InitializeComponent();
        _Bar[0].Checkers = 3;
    }

This puts 3 checkers for Player1 on the bar, just like i wanted.