I have inherited a project that displays 2D data in grid. However, things seem to be done in a wrong way. I have set up a minimum working example for a hall and seats:
Data is stored in a model as a 2D array:
public class DataModel
{
public enum SeatState { Empty, Reserved, Sold }
public SeatState[,] Seats;
public DataModel(int rows, int columns)
{
Seats = new SeatState[columns, rows];
}
}
The displaying Window gets reference to the Seats array in constructor, creates Rectangle objects, inserts them into the grid and stores them for later "rendering" use. When the data change, Render
method is called, walks through the entire 2D array and sets Rectangle styles.
The XAML with style resources:
<Window x:Class="SE_MWE.HallWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_Quora_binding"
mc:Ignorable="d"
Title="HallWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="EmptySeat" TargetType="Rectangle">
<Setter Property="Margin" Value="1" />
<Setter Property="Fill" Value="LightSeaGreen" />
</Style>
<Style x:Key="ReservedSeat" TargetType="Rectangle" BasedOn="{StaticResource EmptySeat}">
<Setter Property="Fill" Value="OrangeRed" />
</Style>
<Style x:Key="SoldSeat" TargetType="Rectangle" BasedOn="{StaticResource EmptySeat}">
<Setter Property="Fill" Value="DarkMagenta" />
</Style>
</Window.Resources>
<Grid x:Name="HallGrid" Margin="10" />
</Window>
And the C# code:
public partial class HallWindow : Window
{
private readonly DataModel.SeatState[,] _seats; //reference to data
private Rectangle[,] _uiSeats; // stored UI elements for rendering
public HallWindow(DataModel.SeatState[,] seats)
{
InitializeComponent();
_seats = seats;
InitializeHall();
RenderHall();
}
private void InitializeHall()
{
_uiSeats = new Rectangle[_seats.GetLength(0), _seats.GetLength(1)];
// define grid rows and columns according to data width and height
for (int i = 0; i < _seats.GetLength(0); i++)
{
HallGrid.ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 0; i < _seats.GetLength(1); i++)
{
HallGrid.RowDefinitions.Add(new RowDefinition());
}
// insert child elements, store them for render
for (int x = 0; x < _seats.GetLength(0); x++)
{
for (int y = 0; y < _seats.GetLength(1); y++)
{
Rectangle UISeat = new Rectangle();
HallGrid.Children.Add(UISeat);
Grid.SetRow(UISeat, y);
Grid.SetColumn(UISeat, x);
_uiSeats[x, y] = UISeat;
}
}
}
public void RenderHall()
{
for (int x = 0; x < _seats.GetLength(0); x++)
{
for (int y = 0; y < _seats.GetLength(1); y++)
{
DataModel.SeatState seatState = _seats[x, y];
Rectangle UISeat = _uiSeats[x, y];
UISeat.Style = FindStyle(seatState);
}
}
}
private Style FindStyle(DataModel.SeatState state)
{
switch (state)
{
case DataModel.SeatState.Empty:
return FindResource("EmptySeat") as Style;
case DataModel.SeatState.Reserved:
return FindResource("ReservedSeat") as Style;
case DataModel.SeatState.Sold:
return FindResource("SoldSeat") as Style;
default:
return null;
}
}
}
This way is definitely not the correct MVVM. I know that instead of manually calling Render() method whenever data change, we should only create a binding between the UI elements and the 2D data, and then the framework takes care of all UI updates automatically. I use this regularly with simple dependency properties and bindings written directly into XAML.
However, I'm confused, how to fix this - how to make this kind of binding.