.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#) 我无法从另一个线程获取更改背景的按钮的主要内容,如果未能解决你的问题,请参考以下文章
C# WPF Datagrid:从 SelectionChanged 事件中获取值