[英]WPF updating ListBox items from method called in different class
几个小时以来,我一直在努力反对WPF ListBox项绑定,希望能得到一些建议。 我的应用程序包含三个主要元素:
TcpClient
发送和接收数据的Player
类。 MainWindow
,用于处理GUI并公开Player
可以调用的方法,以基于网络消息提供用于更新UI的数据。 HostLobby
的UserControl
,其中包含1)一个名为gamesListBox
的ListBox
用于显示客户端通过Player
添加的新游戏,以及2)UI元素,用于添加要向所有客户端广播的新游戏。 我已经确认“上游”作品是可行的。 您可以在HostLobby
上输入新游戏信息, HostLobby
提交,并按预期传播给客户端。 此外,客户端可以正确响应服务器消息,告知服务器已添加新游戏。
问题是,我无法更新gameListBox
。 我在HostLobby
控件和MainWindow
上都装配了测试按钮,以验证绑定是否正常工作。 我似乎无法通过从Player
传递数据来进行更新。 有什么想法我做错了吗?
相关代码:
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
public void AddGameToLobby(string n, int mp)
{
hostLobby.AddGameToList(n, mp);
}
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
<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>
由于正在修改工作线程上的ObservableCollection
,因此未更新ListBox
,这意味着在工作线程上也引发了集合的CollectionChanged
事件。 为了解决这个问题,您需要在UI线程上修改列表。 为此,您有几种选择,但是首先想到的是:
使用Dispatcher.BeginInvoke
在AddGameToList
的HostLobby.cs
,将修改列表的语句放在Dispatcher.BeginInvoke
:
Application.Current.Dispatcher.BeginInvoke((MethodInvoker)(() => games.Add(new Game() { Name = msg })));
使用BindingOperations.EnableCollectionSynchronization(.NET 4.5或更高版本)
首先,创建一个锁对象作为HostLobby
类的私有成员。 然后,在初始化ObservableCollection
,调用以下命令:
BindingOperations.EnableCollectionSynchronization(games, _yourLockObj);
然后,只要在HostLobby
修改列表,就使用该锁。 因此,在这种情况下,您需要更改AddGameToList
的列表修改,以使其使用锁定:
lock (_yourLockObj)
{
games.Add(new Game() { Name = msg });
}
在我看来,后者似乎是一个更好的选择,但是只有在使用.NET 4.5或更高版本时,才可以使用后者。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.