TransWikia.com

Как добавить новый элемент в привязанный ListBox? WPF

Stack Overflow на русском Asked by Andrew Pstvt on December 16, 2020

Я новичок в wpf. У меня есть ListBox, привязанный к ObservableCollection в классе MainViewModel, используемый в Window.DataContext. Как можно изменить эту коллекцию, если при обычном добавлении listbox.Items.add() выдает ошибку, а я не знаю, как получить доступ к коллекции?

ListBox:

<ListBox x:Name="Messages" ItemsSource="{Binding Messages}"
                     Foreground="Black"
                     Background="Transparent">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <UniformGrid Columns="1">
                            <UniformGrid.Resources>
                                <Style TargetType="TextBlock">
                                    <Setter Property="Margin" Value="20 2 20 2"/>
                                    <Setter Property="TextAlignment" Value="Left"/>
                                </Style>
                            </UniformGrid.Resources>
                            <Border Background="Red" CornerRadius="20 5 20 5">
                                <StackPanel>
                                    <TextBlock Text="{Binding Name}" FontSize="15"/>
                                    <TextBlock Text="{Binding Text}" />
                                </StackPanel>
                            </Border>
                        </UniformGrid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

MainViewModel:

 public class MainViewModel : ViewModelBase
 {
    public ObservableCollection<Message> Messages { get; set; }

    public MainViewModel()
    {
        Messages = new ObservableCollection<Message>
        {
            new Message() { Text = "Hello!", Name="Andrew" },
            new Message() { Text = "qwerty!", Name="ilya" },
            new Message() {Text = "123", Name = "user"}
        };
    }
}

Класс message:

public class Message : ViewModelBase
{
    public string Text { get; set; }
    public string Name { get; set; }
}

Класс MainWindow:

public partial class MainWindow : Window, IServiceChatCallback
{
    public bool isconnected = false;
    public ServiceChatClient client;
    public int Id;

    public MainWindow()
    {
        InitializeComponent();
    }
    public bool ConnectUser(string name, string password)
    {
        if (!isconnected)
        {
            try
            {
                client = new ServiceChatClient(new System.ServiceModel.InstanceContext(this));
                Id = client.Connect(name, int.Parse(password));
                if (Id == 0)
                {
                    client = null;
                    return false;
                }
                isconnected = true;
                return true;
            } catch (Exception e)
            {
                MessageBox.Show(e.Message);
                return false;
            }
        } else
            return false;
    }

    private void DisconnectUser()
    {
        if (isconnected)
        {
            try
            {
                client.Disconnect(Id);
            } catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            isconnected = false;
            client = null;
        }
    }

    public void MsgCallback(string message)//Здесь появляются сообщения, которые должны добавляться в listbox
    {
        Messages.Items.Add(new Message() { Text = message, Name = "Andrew" });
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        DisconnectUser();
    }

    private void Text_KeyDown(object sender, KeyEventArgs e)
    {
        
        if (e.Key == Key.Enter)
        {
            try
            {
                client?.SendMessageAsync(Text.Text, Id);
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
            }
            Text.Text = string.Empty;
        }
    }
}

One Answer

Давайте сразу выкинем пока DevExpressMvvm попробуем подружиться с MVVM самостоятельно. Это не так сложно, как казалось бы.

2 основных штуки, которые потребуются для работы - это привязка данных и команды. Чтобы заработала привязка данных, нужна реализация интерфейса INotifyPropertyChanged. В DevExpressMvvm за это отвечает ViewModelBase, но вручную это реализовать не так сложно. Вот реализация:

NotifyPropertyChanged.cs

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Вот и всё, теперь буду наследоваться от этого класса вместо ViewModelBase и на этом можно знакомство с DevExpress прекратить. Когда вы реально будете нуждаться в этом фреймворке - вы поймете, а пока он вам совершенно не нужен.

Далее, для того чтобы жать кнопки и выполнять код, когда кнопка нажата, нужны команды, так как обработчики событий крайне неудобны для использования в MVVM. Для того, чтобы сделать использование команд максимально удобным, есть вот такой класс:

RelayCommand.cs

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

Не я его придумал, но немного привел в порядок. Оба выше показанных класса просто скопируйте в проект.

Далее, MainViewModel - здесь живет вся основная логика приложения, можете считать, что она переехала из класса MainWindow. Я немного дописал вам логику, чтобы вы смогли ознакомиться с тем, что вам может пригодиться сразу.

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged, IServiceChatCallback
{
    public bool isconnected = false;
    public ServiceChatClient client;
    public int Id;

    public ObservableCollection<Message> Messages { get; }
    private string _messageText;
    private ICommand _sendCommand;

    public string MessageText
    {
        get => _messageText;
        set
        {
            _messageText = value;
            OnPropertyChanged(); // Вот зачем INotifyPropertyChanged - ниже в коде я меняю занчение этого свойства, и оно меняется в интерфейсе само
        }
    }

    public ICommand SendCommand => _sendCommand ??= new RelayCommand(parameter =>
    {
        Messages.Add(new Message() { Text = MessageText, Name = "Me", Incoming = false });
        MessageText = "";
        try
        {
            client?.SendMessageAsync(MessageText, Id);
        }
        catch (Exception err)
        {
            MessageBox.Show(err.Message);
        }
    }, parameter => MessageText?.Length > 0); // обратите внимание, что кнопку Send нельзя нажать, пока в строке пусто, это условие здесь именно за этим


    public void MsgCallback(string message)
    {
        Messages.Add(new Message() { Text = message, Name = "User", Incoming = true });
    }

    public bool ConnectUser(string name, string password)
    {
        if (!isconnected)
        {
            try
            {
                client = new ServiceChatClient(new System.ServiceModel.InstanceContext(this));
                Id = client.Connect(name, int.Parse(password));
                if (Id == 0)
                {
                    client = null;
                    return false;
                }
                isconnected = true;
                return true;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                return false;
            }
        }
        else
            return false;
    }

    public void DisconnectUser()
    {
        if (isconnected)
        {
            try
            {
                client.Disconnect(Id);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            isconnected = false;
            client = null;
        }
    }

    public MainViewModel()
    {
        Messages = new ObservableCollection<Message>
        {
            new Message() { Text = "Hello! Hello! Hello! Hello! Hello! Hello! Hello! ", Name="Andrew" , Incoming = true},
            new Message() { Text = "qwerty!", Name="ilya", Incoming = false },
            new Message() {Text = "123", Name = "user", Incoming = true }
        };
    }
}

И добавил свойство в данные, чтобы интерфейс мог отличать входящие и исходящие сообщения

public class Message : NotifyPropertyChanged
{
    private string _text;
    private string _name;
    private bool _incoming;

    public string Text
    {
        get => _text;
        set
        {
            _text = value;
            OnPropertyChanged();
        }
    }
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }
    public bool Incoming
    {
        get => _incoming;
        set
        {
            _incoming = value;
            OnPropertyChanged();
        }
    }
}

Ну и сам интерфейс

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl ItemsSource="{Binding Messages}"
                    Foreground="Black"
                    Background="Transparent">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.Resources>
                            <Style TargetType="TextBlock">
                                <Setter Property="Margin" Value="20 2 20 2"/>
                                <Setter Property="TextAlignment" Value="Left"/>
                            </Style>
                            <Style TargetType="Border">
                                <Setter Property="Margin" Value="5"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Incoming}" Value="True">
                                        <Setter Property="Background" Value="LightPink"/>
                                        <Setter Property="CornerRadius" Value="20 5 20 5"/>
                                        <Setter Property="HorizontalAlignment" Value="Left"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding Incoming}" Value="False">
                                        <Setter Property="Background" Value="AliceBlue"/>
                                        <Setter Property="CornerRadius" Value="5 20 5 20"/>
                                        <Setter Property="HorizontalAlignment" Value="Right"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Grid.Resources>
                        <Border>
                            <StackPanel>
                                <TextBlock Text="{Binding Name}" FontSize="15"/>
                                <TextBlock Text="{Binding Text}" TextWrapping="Wrap" />
                            </StackPanel>
                        </Border>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>
    <Grid Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox Margin="5" Text="{Binding MessageText, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" Command="{Binding SendCommand}"/>
            </TextBox.InputBindings>
        </TextBox>
        <Button Grid.Column="1" Content="Send" Margin="5" Padding="10,5" Command="{Binding SendCommand}" Focusable="False"/>
    </Grid>
</Grid>

Да, сообщения можно посылать нажав мышкой на кнопку, либо просто нажав Enter.

введите сюда описание изображения

Вот класс MainWindow

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        MainViewModel vm = new MainViewModel()
        DataContext = vm;
        this.Closing += (s, e) => { vm.DisconnectUser(); }
    }
}

Correct answer by aepot on December 16, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP