0
votes

i have wpf application, a game of sudoku. I have a model and user control for a game grid (9x9 squares). Because my knowledge of binding is limited and i had not enough time, i decided to make it old way without databinding and manualy synchronize model and view. However it is very unclean and synchronization problems appear.

I decided to convert to proper databinding.

I suppose my grid should be something like itemscontrol (listbox or combobox) but instead of linear list it would layout its items into two dimensional grid.

I am new to binding and i have no idea how to achieve my goal. I have model class which has some general info about current puzzle and contains collection of cells. Each cell has its own propertiues like value, possibilities, state etc.

I have user control of entire grid and user control for each individual cell. I need grid to bind to some properties of my model (eg, disabled,enabled,sudoku-type) and each cell to bind to my corresponding cell - value, background etc.

EDIT: Cell are in observable collection. Each cell has X and Y property. I think they should somehow bind to Grid.Row and Grid.Column properties.

Can you please point me some direction how to continue? Especially creating that itemscontrol and how to bind to it?

Thank you.

2
I've added some code examples to my answer, and now it should be completely clear.Alexander Mavrinsky

2 Answers

1
votes

1) Should it be observable? - no, you could use for example List<List<CellModel>>()
2) If you want to still use array[,] - you may need some kind of converter if you want to use ItemsControl
3) You can use items control with ItemTemplate wich will be an Items control too

hope this will help

Edit 1: Lets create a converter from point 2...

public class ArrayConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var val = value as CellModel[,];
        if (val == null) return null;
        return ToEnumerable(val);
    }

    private IEnumerable<IEnumerable<CellModel>> ToEnumerable(CellModel[,] array)
    {
        var count = array.GetLength(0);

        for (int i = 0; i < array.GetLength(0); ++i)
        {
            yield return GetLine(array, i);
        }
    }

    private IEnumerable<CellModel> GetLine(CellModel[,] array, int line)
    {
        var count = array.GetLength(1);
        for (int i = 0; i < count; ++i)
        {
            yield return array[line, i];
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Edit 2: Your xaml could look like (see point 3):

<Grid>

    <Grid.Resources>
        <Converters:ArrayConverter x:Key="ArrayConverter"/>
    </Grid.Resources>

    <ItemsControl
        ItemsSource="{Binding CellArray, Mode=OneWay, Converter={StaticResource ArrayConverter}}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ItemsControl
                    ItemsSource="{Binding ., Mode=OneWay}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <!-- TODO: Add cell template here -->
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal" IsItemsHost="True"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

Edit 3: added line <StackPanel Orientation="Horizontal" IsItemsHost="True"/> - this will make your lines to be rendered horisontal

Edit 4: your view model could now be something like this:

public class GameViewModel : ViewModelBase
{
    public void Load()
    {
        var array = new CellModel[9, 9];
        for (int i = 0; i < 9; ++i)
        {
            for (int j = 0; j < 9; ++j)
            {
                array[i, j] = new CellModel()
                {
                    //TODO: init properties of the cell[i, j]
                };
            }
        }

        this.CellArray = array;
    }

    CellModel[,] _CellArray;
    public CellModel[,] CellArray
    {
        get
        {
            return _CellArray;
        }
        private set
        {
            if (_CellArray == value) return;
            _CellArray = value;
            NotifyPropertyChanged("CellArray");
        }
    }
}

Let me know if something is still unclear

Edit 5: Depends on your last edit, lets change our XAML:

  <ItemsControl
        ItemsSource="{Binding CellArray, Mode=OneWay}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid
                    Grid.Column="{Binding X}"
                    Grid.Row="{Binding Y}">
                    <!-- TODO: -->
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid IsItemsHost="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition></RowDefinition>
                        <RowDefinition></RowDefinition>
                        <RowDefinition></RowDefinition>
                    </Grid.RowDefinitions>
                </Grid>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
0
votes

You don't need to use an ObservableCollection to bind to a collection, however it is recommended if you want to CollectionChange notification to be automatically implemented.

This means if you update your collection (for example, clearing it and starting a new Game), it will automatically tell the UI that the collection has changed and the UI will redraw itself with the new elements.

You can also Linq statements with ObservableCollections to find a specific element. For example, the following will return the first cell that matches the specified criteria, or null if no item is found

var cell = MyCollection.FirstOrDefault(
    cell => cell.Y == desiredRowIndex && cell.X == desiredColumnIndex);