首页 > 解决方案 > .NET WPF (C#) 我无法从另一个线程获取更改背景的按钮

问题描述

尽管从 UI 线程实例化的线程中使用 Dispatcher,但我似乎无法让按钮更改颜色。

这是我的简单 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>

此外,这是逻辑:

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;
    }
}

}

观察game()主程序的方法。此外,我将按钮称为除按钮之外的单元格start。现在请注意,我总是将任何对 gridGOL或其任何子项的调用封装在Dispatch其他论坛向我解释的操作中。但是,它似乎对 UI 没有任何影响。

谢谢你的帮助。

标签: c#.netwpfmultithreadinguser-interface

解决方案


您的代码陷入while循环。由于它是一个无限循环,因此它无法退出循环并阻止您执行任何进一步的操作。

您可以创建一个DispatcherTimer在 UI 线程中工作的,并在其中创建您的 Game 函数。

DispatcherTimer dispatcherTimer = new DispatcherTimer();

while(true)您可以通过删除游戏功能来避免它卡住。game()您可以将该函数与DispatcherTimer您创建的刻度函数挂钩。

 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;
                    }
                }
            }
    }

当您将以下代码添加到ButtonClick事件中时,按钮颜色在游戏开始前不会改变。

if (!dispatcherTimer.IsEnabled) return;

推荐阅读