Пишем мессенджер на C#. Часть 1. Вёрстка
Клиент-серверная разработка — одна из самых востребованных отраслей программирования. Зная её азы, можно создавать как мессенджеры, так и онлайн-игры.


vlada_maestro / shutterstock
В этой серии статей мы напишем клиент-серверное приложение на C# — простейший мессенджер. Серия состоит из трёх частей:
- Вёрстка приложения — мы создадим графический интерфейс на C# и XAML для Windows.
- Создание WebAPI на ASP.NET — составим базу данных и разработаем серверную часть приложения.
- Объединение клиента и сервера — напишем запросы к серверу и позаботимся, чтобы всё работало как надо.
Язык C# пригодится в разработке чего угодно. Возможности WPF (система создания графических интерфейсов) позволяют создавать красивые и функциональные приложения для Windows, а ASP.NET — мощные серверные приложения.
Я постараюсь объяснить подробно, но охватить всё невозможно, поэтому вам нужно знать основы C#, ООП, ASP.NET, WPF и работы в Visual Studio.
Вот несколько статей, с которыми стоит ознакомиться, если вы чего-то не знаете:
Структура приложения
Мы не рассматриваем регистрацию, поиск контактов, хранение сообщений, продвинутый дизайн. Вместо этого вы узнаете азы создания клиент-серверных приложений и сможете сделать всё самостоятельно.
Исходный код мессенджера вы найдете на GitHub.
Приложение мы поделим на экраны:
- экран авторизации;
- экран с контактами;
- экран с чатом.
Экран — это элемент Border, который по умолчанию скрыт от пользователя. Виден будет только активный экран.
На экране авторизации пользователь сможет ввести логин и пароль, чтобы войти. Если он ввёл верные данные, то перейдёт на экран с контактами, иначе — увидит сообщение об ошибке.
На экране с контактами видны имена других пользователей, с которыми ведётся переписка. Чат открывается при нажатии на имя другого пользователя.
На экране с чатом видна переписка с одним конкретным контактом. Пользователь может написать и отправить новое сообщение или вернуться к экрану с контактами.
Верстаем экран авторизации
Начнём с определения стилей. В них минимально обозначим, как должны выглядеть элементы, и сразу укажем, что экраны по умолчанию должны быть скрыты:
<Style x:Key="Screen">
<Style.Setters>
<Setter Property="Border.Visibility" Value="Hidden" />
<Setter Property="Border.Background" Value="#151515" />
</Style.Setters>
</Style>
<Style x:Key="LoginPanel">
<Style.Setters>
<Setter Property="StackPanel.Orientation" Value="Vertical" />
<Setter Property="StackPanel.VerticalAlignment" Value="Center" />
</Style.Setters>
</Style>
<Style x:Key="TextBoxBase">
<Style.Setters>
<Setter Property="TextBox.Background" Value="#333" />
<Setter Property="TextBox.Foreground" Value="#f6f6f6" />
<Setter Property="TextBox.Margin" Value="5"/>
<Setter Property="TextBox.Padding" Value="15 10"/>
<Setter Property="TextBox.HorizontalAlignment" Value="Center" />
<Setter Property="TextBox.Width" Value="250" />
</Style.Setters>
</Style>
<Style x:Key="ButtonBase" >
<Style.Setters>
<Setter Property="Button.Background" Value="#333" />
<Setter Property="Button.Foreground" Value="#f6f6f6" />
<Setter Property="Button.Margin" Value="5"/>
<Setter Property="Button.Padding" Value="50 10"/>
<Setter Property="Button.HorizontalAlignment" Value="Center" />
<Setter Property="Button.FontSize" Value="14" />
</Style.Setters>
</Style>
Теперь сверстаем сам экран авторизации — он должен быть видимым:
<Border Style="{StaticResource Screen}" Name="LoginScreen" Visibility="Visible">
<StackPanel Style="{StaticResource LoginPanel}">
<TextBlock Text="Login" Style="{StaticResource HeaderBlock}" />
<TextBox Style="{StaticResource TextBoxBase}" Name="LoginBox" />
<PasswordBox Style="{StaticResource TextBoxBase}" Name="PasswordBox"/>
<Button Content="Enter" Style="{StaticResource ButtonBase}" Name="LoginButton" Click="LoginButton_Click" IsDefault="True"/>
<TextBlock Text="" Name="LoginMessageBlock" Style="{StaticResource WarningBlock}" Visibility="Hidden"/>
</StackPanel>
</Border>
Давайте посмотрим, как это выглядит:
Теперь напишем обработчик для кнопки Enter:
//Обработчик нажатия на кнопку Login
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
//Пока используем тестовые данные
if(LoginBox.Text == "admin" && PasswordBox.Password == "12345")
{
//Если логин и пароль верные, то переходим на другой экран
Open(ContactsScreen);
}
else
{
//Иначе выводим сообщение об ошибке авторизации
LoginMessageBlock.Text = "Wrong login or password!";
LoginMessageBlock.Visibility = Visibility.Visible;
}
}
//Метод для открытия другого экрана
private void Open(Border screen)
{
//Делаем все экраны невидимыми
LoginScreen.Visibility = Visibility.Hidden;
ContactsScreen.Visibility = Visibility.Hidden;
ChatScreen.Visibility = Visibility.Hidden;
//Делаем видимым необходимый экран
screen.Visibility = Visibility.Visible;
}
У вас должны быть экраны с именами ContactsScreen и ChatScreen, чтобы метод Open () работал корректно. Для этого достаточно создать два пустых элемента Border.
Вот как выглядит экран авторизации при вводе неверных данных:
Дальше сверстаем экран с контактами.
Верстаем экран с контактами
Экран с контактами разделим на два ряда с помощью Grid — заголовок в первом ряду и список контактов во втором. Список — это элемент ListBox, в котором перечислены контакты.
<Border Name="ContactsScreen" Style="{StaticResource Screen}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource HeaderBorder}">
<TextBlock Style="{StaticResource HeaderBlock}" Text="Contacts" VerticalAlignment="Center"/>
</Border>
<Border Grid.Row="1">
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"
Style="{StaticResource ContactsList}" Name="ContactsList" SelectionChanged="ContactsList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<DockPanel LastChildFill="True">
<Image Style="{StaticResource ContactImage}" DockPanel.Dock="Left"></Image>
<TextBlock Text="{Binding Name}" Style="{StaticResource ContactName}" DockPanel.Dock="Right"/>
</DockPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</Border>
Здесь описаны не все элементы списка, а только шаблон для них, который будет использоваться во время привязки данных. Этот шаблон понадобится в готовом приложении, но для удобства вёрстки вы можете подготовить тестовые данные.
Зададим стили:
<Style x:Key="ContactsList" BasedOn="{StaticResource TextBlockBase}">
<Style.Setters>
<Setter Property="ListBox.Background" Value="#151515"/>
<Setter Property="ListBox.BorderThickness" Value="0"/>
</Style.Setters>
</Style>
<Style x:Key="ContactImage" BasedOn="{StaticResource TextBlockBase}">
<Style.Setters>
</Style.Setters>
</Style>
<Style x:Key="ContactName" BasedOn="{StaticResource TextBlockBase}">
<Style.Setters>
<Setter Property="TextBlock.HorizontalAlignment" Value="Left"/>
</Style.Setters>
</Style>
<Style x:Key="HeaderBorder">
<Style.Setters>
<Setter Property="Border.Background" Value="#222" />
</Style.Setters>
</Style>
Теперь остаётся написать метод, который открывает чат с контактом. Этот метод вызывается, когда пользователь выберет пункт из списка.
private void ContactsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Метод вызывается, когда меняется индекс выделенного элемента
//При выделении элемент списка будет подсвечиваться
//Чтобы убрать это, мы будем менять индекс на -1
//Чтобы метод не срабатывал повторно, мы проверяем, чтобы индекс был больше или равен 0
if(ContactsList.SelectedIndex >= 0)
{
//Тут будет код загрузки сообщений из чата
//Сбрасываем индекс
ContactsList.SelectedIndex = -1;
Open(ChatScreen);
}
}
Верстаем экран с чатом
Чат похож на экран с контактами, но немного дополненный:
Grid делит экран на три части: заголовок, чат и поле ввода. В заголовке — имя собеседника и кнопка «Назад». В чате выведены сообщения с помощью ListBox, а внизу находятся поле ввода и кнопка отправки.
<Border Name="ChatScreen" Style="{StaticResource Screen}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource HeaderBorder}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button Style="{StaticResource NavButton}" Grid.Column="0" Name="BackButton" Content="←" Click="BackButton_Click"/>
<TextBlock Style="{StaticResource HeaderBlock}" Text="" VerticalAlignment="Center" Name="ChatName" Grid.Column="1"/>
</Grid>
</Border>
<Border Grid.Row="1">
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"
Style="{StaticResource ContactsList}" Name="MessagesList" Focusable="False"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Border Style="{StaticResource MessageBorder}" HorizontalAlignment="{Binding Alignment}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Text}" Style="{StaticResource MessageText}"/>
<TextBlock Text="{Binding Date}" Style="{StaticResource MessageDate}"/>
</StackPanel>
</Border>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<Border Grid.Row="2" Style="{StaticResource HeaderBorder}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Name="MessageBox" Style="{StaticResource MessageBox}" Grid.Column="0"/>
<Button Style="{StaticResource NavButton}" Grid.Column="1" Name="SendButton" Content="→" Click="SendButton_Click"/>
</Grid>
</Border>
</Grid>
</Border>
Тут, как на экране с контактами, пока нет данных -— только шаблон для их вывода. Немного стилей:
<Style x:Key="MessageBorder">
<Style.Setters>
<Setter Property="Border.Background" Value="#555" />
<Setter Property="Border.CornerRadius" Value="13" />
<Setter Property="Border.MinWidth" Value="100" />
<Setter Property="Border.MaxWidth" Value="300" />
<Setter Property="Border.Padding" Value="2" />
</Style.Setters>
</Style>
<Style x:Key="MessageText" BasedOn="{StaticResource TextBlockBase}">
<Style.Setters>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.Margin" Value="0" />
</Style.Setters>
</Style>
<Style x:Key="MessageDate" BasedOn="{StaticResource TextBlockBase}">
<Style.Setters>
<Setter Property="TextBlock.HorizontalAlignment" Value="Right" />
<Setter Property="TextBlock.FontSize" Value="8" />
<Setter Property="TextBlock.Margin" Value="0" />
</Style.Setters>
</Style>
Остаётся только обработать события для кнопок отправки и навигации:
private void SendButton_Click(object sender, RoutedEventArgs e)
{
string text = "";
if(!string.IsNullOrEmpty(MessageBox.Text))
{
text = MessageBox.Text.Trim();
}
if(!string.IsNullOrEmpty(text))
{
bool result = true;
if(result)
{
MessageBox.Text = "";
}
}
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
Open(ContactsScreen);
}
Заключение
У нас получился довольно минималистичный дизайн, который вы можете доработать как вам нравится. Дальше нам предстоит написать серверную часть приложения, а потом связать их вместе.