Пишем мессенджер на C#. Часть 1. Вёрстка
2026-02-21 05:59 Diff

#Руководства

  • 24 июн 2020
  • 0

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

vlada_maestro / shutterstock

Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.

В этой серии статей мы напишем клиент-серверное приложение на 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); }

У нас получился довольно минималистичный дизайн, который вы можете доработать как вам нравится. Дальше нам предстоит написать серверную часть приложения, а потом связать их вместе.

Практический курс: «Веб-вёрстка» Узнать о курсе