I cannot seem to get my buttons to change colour despite using the Dispatcher from a thread instantiated from the UI thread.
Here is my simple XAML:
<Window x:Class="GOL.MainWindow"
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:GOL"
mc:Ignorable="d"
Title="MainWindow">
<Grid Name="GOL">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<!--extra row for START button-->
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
Further, here is the logic:
using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace GOL
{
public partial class MainWindow : Window
{
private const int LENGTH = 30;
private Thread gameThread;
public MainWindow()
{
gameThread = null;
InitializeComponent();
//Load the game grid:
Button cell;
for (int i = 0; i < LENGTH; i) {
for (int j = 0; j < LENGTH; j) {
cell = new Button();
cell.Background = Brushes.Black;
cell.Name = "cell_" ((LENGTH * i) j).ToString();
cell.Click = new RoutedEventHandler(Button_Click);
Grid.SetRow(cell, i);
Grid.SetColumn(cell, j);
//adds children in same order as in name
GOL.Children.Add(cell);
}
}
//finally add start button:
Button start = new Button();
start.Name = "start";
start.Content = "GO!";
start.Click = new RoutedEventHandler(Button_Start);
Grid.SetRow(start, (LENGTH 1));
Grid.SetColumn(start, (LENGTH/2));
GOL.Children.Add(start);
}
/**
* Changes state of cell
*/
private void Button_Click(object sender, RoutedEventArgs e)
{
Button cell = (Button)sender;
if (cell.Background.Equals(Brushes.Black))
cell.Background = Brushes.White;
else
cell.Background = Brushes.Black;
}
/**
*
*/
private void Button_Start(object sender, RoutedEventArgs e)
{
Button start = (Button)sender;
if (start.Content.Equals("GO!")) {
start.Content = "STOP";
gameThread = new Thread(game);
gameThread.Start();
}
else {
start.Content = "GO!";
try {
gameThread.Abort();
gameThread.Join();
} catch (ThreadAbortException){
;//Assumption: gameThread halted
}
}
}
private void game()
{
int neighbours = 0;
Button cell = null;
while (true)
{
//forAll <i, j> of the board:
for (int i = 0; (i < LENGTH); i)
{
for (int j = 0; (j < LENGTH); j)
{
//board is owned by UI thread
Dispatcher.Invoke(() => {
//reference cell (i, j)
cell = (Button)GOL.Children[(LENGTH * i) j];
});
//get neighbour count
neighbours = liveNeighbours(i, j);
//State transition w/r neighbour count:
switch (neighbours) {
case 0:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 1:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 2:
//no change
break;
case 3:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.White
));
break;
case 4:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 5:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 6:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 7:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 8:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
}
}
}
}
}
private int liveNeighbours(int x, int y)
{
int living = 0, location;
Button neighbour = null;
for (int Xoff = -1; (Xoff < 2); Xoff) {
for (int Yoff = -1; (Yoff < 2); Yoff) {
location = ((x Xoff) * LENGTH) y Yoff;
if ((Xoff == 0) && (Yoff == Xoff))
;//skip if self
else if ((location < 0) || (location > (Math.Pow(LENGTH, 2) - 1)))
;//skip if outside grid
else {
Dispatcher.Invoke(() => {
neighbour = (Button)GOL.Children[location];
if (neighbour.Background.Equals(Brushes.White))
living;//add to living iff white
});
}
}
}
return living;
}
}
}
Observe the method game()
for the main procedure. Also, I refer to the buttons as cells except for the start
button.
Now notice I always encapsulate any call to the grid GOL
, or any child of it, in a Dispatch
operation as was explained to me by other forums. However, it does not seem to have any effect on the UI.
Thanks for any help.
CodePudding user response:
You should take a second look at your switch cases.
Change the Brushes.Black
in case 0:
to Brushes.White
and the dispatcher will update the UI without any problems.
CodePudding user response:
Your code gets stuck in a while
loop. Since it is an endless loop, it cannot exit the loop and prevents you from doing any further action.
You can create a DispatcherTimer
that works in the UI thread and create your Game function inside it.
DispatcherTimer dispatcherTimer = new DispatcherTimer();
You can avoid it getting stuck by deleting while(true)
inside the game function. You can hook the game()
function to the DispatcherTimer
tick function you created.
private void Button_Start(object sender, RoutedEventArgs e)
{
Button start = (Button)sender;
if (start.Content.Equals("GO!"))
{
start.Content = "STOP";
dispatcherTimer.Start();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(1);
dispatcherTimer.Tick = DispatcherTimer_Tick;
dispatcherTimer.Start();
}
else
{
start.Content = "GO!";
try
{
dispatcherTimer.Stop();
}
catch (ThreadAbortException)
{
;//Assumption: gameThread halted
}
}
}
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
game();
}
private void game()
{
int neighbours = 0;
Button cell = null;
//forAll <i, j> of the board:
for (int i = 0; (i < LENGTH); i)
{
for (int j = 0; (j < LENGTH); j)
{
//board is owned by UI thread
Dispatcher.Invoke(() => {
//reference cell (i, j)
cell = (Button)GOL.Children[(LENGTH * i) j];
});
//get neighbour count
neighbours = liveNeighbours(i, j);
//State transition w/r neighbour count:
switch (neighbours)
{
case 0:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 1:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 2:
//no change
break;
case 3:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.White
));
break;
case 4:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 5:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 6:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 7:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
case 8:
cell.Dispatcher.BeginInvoke((Action)(() =>
cell.Background = Brushes.Black
));
break;
}
}
}
}
When you add the following code to the ButtonClick
event, the button colors will not be changed before the game starts.
if (!dispatcherTimer.IsEnabled) return;