Search code examples
c#wpfxamlxaml-binding

WPF XAML Change multiple ellipses colors with data binding


I'm currently creating a connect-4 game in order to learn WPF & XAML. I made the UI but I'm stuck on a problem.

Below you can see an extract of the XAML code concerning the board of the game :

<Grid DockPanel.Dock="Bottom" Background="#FF1506A4" MouseLeftButtonUp="Grid_MouseLeftButtonUp_1">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            ... 5 more rows
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            ... 6 more columns
        </Grid.ColumnDefinitions>
        <Ellipse Grid.Row="0" Grid.Column="0" Fill="White" Margin="8"/>
        ... 41 more ellipses
</Grid>

The board is stored in an array of Token (an enumeration with Empty, Red and Yellow) in the class GameState.

The colors of the Ellipses are provided using the class SolidBrushColor.

My problem is that I don't know how to change the color of the ellipses according to the game model.

I guess I should use data binding but I have to convert colors from the type Token to the type SolidBrushColor before binding the data. I think it could be achieved using some DataObjectProvider objects but it seems over complicated to create 42 DataObjectProvider objects for such a simple task...

So what would be the correct solution according to the best pratices ?


Solution

  • You'll want to use some sort of ViewModel at the backend, and then leverage DataBinding.

    Assuming the following (contrived) ViewModel structure that represents a Connect Four board.

    BoardViewModel.cs

    public class BoardViewModel
    {
        public BoardViewModel()
        {
            var rand = new Random();
            Squares = Enumerable
                .Range(1, 42)
                .Select(a => new SquareViewModel() { Token = rand.Next(-1, 2) })
                .ToList();
        }
    
        public List<SquareViewModel> Squares { get; set; }
    }
    

    SquareViewModel.cs

    public class SquareViewModel : INotifyPropertyChanged
    {
        private int _Token;
        public int Token
        {
            get
            {
                return _Token;
            }
            set
            {
                if (_Token.Equals(value)) return;
    
                _Token = value;
                RaisePropertyChanged("Token");
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            var handlers = PropertyChanged;
            if (handlers != null)
            {
                var args = new PropertyChangedEventArgs(property);
                handlers(this, args);
            }
        }
    }
    

    Then you can use the following XAML to represent your board.

    MainWindow.xaml

    <Window 
        x:Class="ConnectFour.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        mc:Ignorable="d" 
        xmlns:ConnectFour="clr-namespace:ConnectFour"
        Title="MainWindow" Height="350" Width="525"
        d:DataContext="{d:DesignInstance Type={x:Type ConnectFour:BoardViewModel}, IsDesignTimeCreatable=True}">
        <ItemsControl
            ItemsSource="{Binding Squares}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Ellipse
                        Stroke="Magenta">
                        <Ellipse.Style>
                            <Style TargetType="Ellipse">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Token}" Value="0">
                                        <Setter Property="Fill" Value="Black" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding Token}" Value="1">
                                        <Setter Property="Fill" Value="Red" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Ellipse.Style>
                    </Ellipse>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid IsItemsHost="True" Columns="6" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Window>
    

    The important things to note are:

    • The DataTriggers that set the colour of the Ellipses based on the value of the Token property of a Square.
    • The usage of an ItemsPanel with a UniformGrid backing it up.
    • The implementation of INotifyPropertyChanged in the SquareViewModel so that when the value of Token changes the View represents that.
    • The usage of the d:DataContext attribute, which is setting the design time DataContext of the form to an instance of BoardViewModel (which initialises itself to random tokens).

    At run-time, you will want to set the DataContext of your board View to a real instance of the BoardViewModel (or whatever your ViewModel is called), but the basic idea of how to change the colour of the tokens is present.