WPF обновляет элементы ListBox из метода, вызываемого в другом классе - PullRequest
0 голосов
/ 30 мая 2018

Я уже несколько часов бьюсь головой о привязке элемента WPF ListBox и надеюсь получить совет.Мое приложение имеет три основных элемента:

  1. Класс Player, который отправляет и получает данные через TcpClient.
  2. A MainWindow, который обрабатывает графический интерфейс и предоставляет методы, которыеPlayer может вызвать данные для обновления пользовательского интерфейса на основе сетевых сообщений.
  3. A UserControl с именем HostLobby, который содержит 1) ListBox с именем gamesListBox для отображения новых игр в том виде, как они есть.добавлены клиентами через Player и 2) элементы пользовательского интерфейса для добавления новой игры, которая будет транслироваться всем клиентам.

Я подтвердил, что часть «вверх по течению» работает.Вы можете ввести новую информацию об игре в 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>

1 Ответ

0 голосов
/ 30 мая 2018

Ваш ListBox не обновляется, поскольку вы изменяете ObservableCollection в рабочем потоке, что означает, что событие CollectionChanged коллекции также вызывается в рабочем потоке.Чтобы исправить это, вам нужно изменить свой список в потоке пользовательского интерфейса.Для этого у вас есть несколько вариантов, но первые, которые приходят на ум, это:

Использование 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 или более позднюю версию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...