Home > OS >  .NET WPF (C#) I Cannot Get The Button To Change Background from Another Thread
.NET WPF (C#) I Cannot Get The Button To Change Background from Another Thread

Time:10-04

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;
  • Related