[英]WPF updating ListBox items from method called in different class
I have been banging my head against WPF ListBox item binding for hours and am hoping to get some advice. 几个小时以来,我一直在努力反对WPF ListBox项绑定,希望能得到一些建议。 My application has three main elements:
我的应用程序包含三个主要元素:
Player
class that sends and receives data over a TcpClient
. TcpClient
发送和接收数据的Player
类。 MainWindow
that handles the GUI and exposes methods that Player
can call to provide data for updating the UI based on network messages. MainWindow
,用于处理GUI并公开Player
可以调用的方法,以基于网络消息提供用于更新UI的数据。 UserControl
called HostLobby
that contains 1) a ListBox
called gamesListBox
for displaying new games as they are added by clients via Player
and 2) UI elements for adding a new game to be broadcast to all clients. HostLobby
的UserControl
,其中包含1)一个名为gamesListBox
的ListBox
用于显示客户端通过Player
添加的新游戏,以及2)UI元素,用于添加要向所有客户端广播的新游戏。 I have confirmed that the "upstream" piece works. 我已经确认“上游”作品是可行的。 You can enter new game information on
HostLobby
, submit, and it propagates to clients as expected. 您可以在
HostLobby
上输入新游戏信息, HostLobby
提交,并按预期传播给客户端。 In addition, clients respond properly to server messages telling them a new game has been added. 此外,客户端可以正确响应服务器消息,告知服务器已添加新游戏。
The problem is, I cannot get gameListBox
to update. 问题是,我无法更新
gameListBox
。 I rigged up test buttons on both the HostLobby
control and MainWindow
to verify that binding is working properly - which it is. 我在
HostLobby
控件和MainWindow
上都装配了测试按钮,以验证绑定是否正常工作。 I just can't seem to update by passing data from Player
. 我似乎无法通过从
Player
传递数据来进行更新。 Any ideas what I'm doing wrong? 有什么想法我做错了吗?
Relevant code: 相关代码:
Player.cs Player.cs
public void AddGameToLobby(string name, int mp)
{
// name and mp are provided by the network message handler and work fine
WriteToLog("Attempting to add game to host lobby", true);
mainWindow.AddGameToLobby(name, mp);
}
MainWindow.cs MainWindow.cs
public void AddGameToLobby(string n, int mp)
{
hostLobby.AddGameToList(n, mp);
}
HostLobby.cs HostLobby.cs
private MainWindow parent; // used to call an AddGame event when client adds a game
public ObservableCollection<Game> games;
public class Game
{
public string Name { get; set; }
}
public HostLobby(MainWindow mw)
{
InitializeComponent();
parent = mw;
games = new ObservableCollection<Game>();
// add some test items - this works. the ListBox updates properly
games.Add(new Game() { Name = "game1" });
games.Add(new Game() { Name = "game2" });
games.Add(new Game() { Name = "game3" });
gamesListBox.ItemsSource = games;
}
public void AddGameToList(string n, int maxp)
{
// called to announce a new game added by another client
// call stack is Player.AddGameToLobby -> MainWindow.AddGameToLobby -> this.AddGameToList
string msg = String.Format("{0} (0/{1})", n, maxp.ToString());
games.Add(new Game() { Name = msg });
}
HostLobby.xaml HostLobby.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="50" Orientation="Vertical" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Label HorizontalContentAlignment="Right" Width="80">Game Name:</Label>
<TextBox Name="gameNameTextBox" VerticalContentAlignment="Center" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Label HorizontalContentAlignment="Right" Width="80">Max Players:</Label>
<TextBox Name="maxPlayersTextBox" VerticalContentAlignment="Center" Width="200"/>
</StackPanel>
<Button Name="addGameButton" Click="addGameButton_Click" Margin="0,20,0,0" Width="200" Height="30">Add Game</Button>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="50" Grid.Column="1">
<Label>Current Games</Label>
<ListBox Name="gamesListBox" MinHeight="200">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Your ListBox
is not being updated because you're modifying the ObservableCollection
on a worker thread, which means that the collection's CollectionChanged
event is being raised on the worker thread as well. 由于正在修改工作线程上的
ObservableCollection
,因此未更新ListBox
,这意味着在工作线程上也引发了集合的CollectionChanged
事件。 To remedy this, you need to modify your list on the UI thread. 为了解决这个问题,您需要在UI线程上修改列表。 To achieve this, you have a few options, but the first ones that come to mind are:
为此,您有几种选择,但是首先想到的是:
Using Dispatcher.BeginInvoke 使用Dispatcher.BeginInvoke
In AddGameToList
in HostLobby.cs
, put the statement that modifies the list inside a Dispatcher.BeginInvoke
: 在
AddGameToList
的HostLobby.cs
,将修改列表的语句放在Dispatcher.BeginInvoke
:
Application.Current.Dispatcher.BeginInvoke((MethodInvoker)(() => games.Add(new Game() { Name = msg })));
Using BindingOperations.EnableCollectionSynchronization (.NET 4.5 or later) 使用BindingOperations.EnableCollectionSynchronization(.NET 4.5或更高版本)
First, create a lock object as a private member of your HostLobby
class. 首先,创建一个锁对象作为
HostLobby
类的私有成员。 Then, after initializing your ObservableCollection
, call the following: 然后,在初始化
ObservableCollection
,调用以下命令:
BindingOperations.EnableCollectionSynchronization(games, _yourLockObj);
Then, use the lock whenever you modify the list within HostLobby
. 然后,只要在
HostLobby
修改列表,就使用该锁。 So in this instance, you'd want to change the list modification in AddGameToList
such that it uses the lock: 因此,在这种情况下,您需要更改
AddGameToList
的列表修改,以使其使用锁定:
lock (_yourLockObj)
{
games.Add(new Game() { Name = msg });
}
The latter seems like a better choice in my opinion, but it is only available if you're using .NET 4.5 or later. 在我看来,后者似乎是一个更好的选择,但是只有在使用.NET 4.5或更高版本时,才可以使用后者。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.