.NET WPF (C#) 我无法从另一个线程获取更改背景的按钮

Posted

技术标签:

【中文标题】.NET WPF (C#) 我无法从另一个线程获取更改背景的按钮【英文标题】:.NET WPF (C#) I Cannot Get The Button To Change Background from Another Thread 【发布时间】:2021-11-24 08:34:53 【问题描述】:

尽管从 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 按钮之外,我将按钮称为单元格。 现在请注意,我总是将任何对网格 GOL 或其任何子节点的调用封装在 Dispatch 操作中,正如其他论坛向我解释的那样。但是,它似乎对 UI 没有任何影响。

感谢您的帮助。

【问题讨论】:

【参考方案1】:

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

您可以创建一个在 UI 线程中工作的 DispatcherTimer,并在其中创建您的 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;

【讨论】:

以上是关于.NET WPF (C#) 我无法从另一个线程获取更改背景的按钮的主要内容,如果未能解决你的问题,请参考以下文章

将命令行参数从 VB 传递到 C# WPF 应用程序

从另一个线程更新 oxyplot 模型

如何从另一个 .NET 进程获取对象的句柄?

C# WPF Datagrid:从 SelectionChanged 事件中获取值

在具有 ui/非 ui 线程差异的 WPF 中使用 PInvoke

C# 从另一个线程调用 form.show()