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;
}
}
}
Давайте сразу выкинем пока 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
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP