HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#Руководства</a></p>
1 <p><a>#Руководства</a></p>
2 <ul><li>24 июн 2020</li>
2 <ul><li>24 июн 2020</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Клиент-серверная разработка - одна из самых востребованных отраслей программирования. Зная её азы, можно создавать как мессенджеры, так и онлайн-игры.</p>
4 </ul><p>Клиент-серверная разработка - одна из самых востребованных отраслей программирования. Зная её азы, можно создавать как мессенджеры, так и онлайн-игры.</p>
5 <p>vlada_maestro / shutterstock</p>
5 <p>vlada_maestro / shutterstock</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7 <p>В этой серии статей мы напишем клиент-серверное приложение на C# - простейший мессенджер. Серия состоит из трёх частей:</p>
7 <p>В этой серии статей мы напишем клиент-серверное приложение на C# - простейший мессенджер. Серия состоит из трёх частей:</p>
8 <ul><li>Вёрстка приложения - мы создадим графический интерфейс на C# и XAML для Windows.</li>
8 <ul><li>Вёрстка приложения - мы создадим графический интерфейс на C# и XAML для Windows.</li>
9 <li>Создание WebAPI на ASP.NET - составим базу данных и разработаем серверную часть приложения.</li>
9 <li>Создание WebAPI на ASP.NET - составим базу данных и разработаем серверную часть приложения.</li>
10 <li>Объединение клиента и сервера - напишем запросы к серверу и позаботимся, чтобы всё работало как надо.</li>
10 <li>Объединение клиента и сервера - напишем запросы к серверу и позаботимся, чтобы всё работало как надо.</li>
11 </ul><p>Язык C# пригодится в разработке чего угодно. Возможности WPF<em>(система создания графических интерфейсов)</em>позволяют создавать красивые и функциональные приложения для Windows, а ASP.NET - мощные серверные приложения.</p>
11 </ul><p>Язык C# пригодится в разработке чего угодно. Возможности WPF<em>(система создания графических интерфейсов)</em>позволяют создавать красивые и функциональные приложения для Windows, а ASP.NET - мощные серверные приложения.</p>
12 <p>Я постараюсь объяснить подробно, но охватить всё невозможно, поэтому вам нужно знать основы C#, ООП, ASP.NET, WPF и работы в Visual Studio.</p>
12 <p>Я постараюсь объяснить подробно, но охватить всё невозможно, поэтому вам нужно знать основы C#, ООП, ASP.NET, WPF и работы в Visual Studio.</p>
13 <p>Вот несколько статей, с которыми стоит ознакомиться, если вы чего-то не знаете:</p>
13 <p>Вот несколько статей, с которыми стоит ознакомиться, если вы чего-то не знаете:</p>
14 <ul><li><a>ООП в C#</a><em>(серия статей).</em></li>
14 <ul><li><a>ООП в C#</a><em>(серия статей).</em></li>
15 <li><a>Как за час создать сайт на ASP.NET</a>.</li>
15 <li><a>Как за час создать сайт на ASP.NET</a>.</li>
16 <li><a>Что такое MVVM: проектирование приложений для Windows</a>.</li>
16 <li><a>Что такое MVVM: проектирование приложений для Windows</a>.</li>
17 <li><a>Асинхронное программирование: Как работает процессор</a><em>(серия статей).</em></li>
17 <li><a>Асинхронное программирование: Как работает процессор</a><em>(серия статей).</em></li>
18 </ul><p>Мы не<strong></strong>рассматриваем регистрацию, поиск контактов, хранение сообщений, продвинутый дизайн. Вместо этого вы узнаете азы создания клиент-серверных приложений и сможете сделать всё самостоятельно.</p>
18 </ul><p>Мы не<strong></strong>рассматриваем регистрацию, поиск контактов, хранение сообщений, продвинутый дизайн. Вместо этого вы узнаете азы создания клиент-серверных приложений и сможете сделать всё самостоятельно.</p>
19 <p>Исходный код мессенджера вы найдете на <a>GitHub</a>.</p>
19 <p>Исходный код мессенджера вы найдете на <a>GitHub</a>.</p>
20 <p>Приложение мы поделим на экраны:</p>
20 <p>Приложение мы поделим на экраны:</p>
21 <ul><li>экран авторизации;</li>
21 <ul><li>экран авторизации;</li>
22 <li>экран с контактами;</li>
22 <li>экран с контактами;</li>
23 <li>экран с чатом.</li>
23 <li>экран с чатом.</li>
24 </ul><p><strong>Экран</strong> - это элемент<em>Border</em>, который по умолчанию скрыт от пользователя. Виден будет только активный экран.</p>
24 </ul><p><strong>Экран</strong> - это элемент<em>Border</em>, который по умолчанию скрыт от пользователя. Виден будет только активный экран.</p>
25 <p>На <strong>экране авторизации</strong>пользователь сможет ввести логин и пароль, чтобы войти. Если он ввёл верные данные, то перейдёт на экран с контактами, иначе - увидит сообщение об ошибке.</p>
25 <p>На <strong>экране авторизации</strong>пользователь сможет ввести логин и пароль, чтобы войти. Если он ввёл верные данные, то перейдёт на экран с контактами, иначе - увидит сообщение об ошибке.</p>
26 <p>На <strong>экране с контактами</strong>видны имена других пользователей, с которыми ведётся переписка. Чат открывается при нажатии на имя другого пользователя.</p>
26 <p>На <strong>экране с контактами</strong>видны имена других пользователей, с которыми ведётся переписка. Чат открывается при нажатии на имя другого пользователя.</p>
27 <p>На <strong>экране с чатом</strong>видна переписка с одним конкретным контактом. Пользователь может написать и отправить новое сообщение или вернуться к экрану с контактами.</p>
27 <p>На <strong>экране с чатом</strong>видна переписка с одним конкретным контактом. Пользователь может написать и отправить новое сообщение или вернуться к экрану с контактами.</p>
28 <p>Начнём с определения стилей. В них минимально обозначим, как должны выглядеть элементы, и сразу укажем, что экраны по умолчанию должны быть скрыты:</p>
28 <p>Начнём с определения стилей. В них минимально обозначим, как должны выглядеть элементы, и сразу укажем, что экраны по умолчанию должны быть скрыты:</p>
29 &lt;Style x:Key="Screen"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Visibility" Value="Hidden" /&gt; &lt;Setter Property="Border.Background" Value="#151515" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="LoginPanel"&gt; &lt;Style.Setters&gt; &lt;Setter Property="StackPanel.Orientation" Value="Vertical" /&gt; &lt;Setter Property="StackPanel.VerticalAlignment" Value="Center" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="TextBoxBase"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBox.Background" Value="#333" /&gt; &lt;Setter Property="TextBox.Foreground" Value="#f6f6f6" /&gt; &lt;Setter Property="TextBox.Margin" Value="5"/&gt; &lt;Setter Property="TextBox.Padding" Value="15 10"/&gt; &lt;Setter Property="TextBox.HorizontalAlignment" Value="Center" /&gt; &lt;Setter Property="TextBox.Width" Value="250" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ButtonBase" &gt; &lt;Style.Setters&gt; &lt;Setter Property="Button.Background" Value="#333" /&gt; &lt;Setter Property="Button.Foreground" Value="#f6f6f6" /&gt; &lt;Setter Property="Button.Margin" Value="5"/&gt; &lt;Setter Property="Button.Padding" Value="50 10"/&gt; &lt;Setter Property="Button.HorizontalAlignment" Value="Center" /&gt; &lt;Setter Property="Button.FontSize" Value="14" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Теперь сверстаем сам экран авторизации - он должен быть видимым:</p>
29 &lt;Style x:Key="Screen"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Visibility" Value="Hidden" /&gt; &lt;Setter Property="Border.Background" Value="#151515" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="LoginPanel"&gt; &lt;Style.Setters&gt; &lt;Setter Property="StackPanel.Orientation" Value="Vertical" /&gt; &lt;Setter Property="StackPanel.VerticalAlignment" Value="Center" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="TextBoxBase"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBox.Background" Value="#333" /&gt; &lt;Setter Property="TextBox.Foreground" Value="#f6f6f6" /&gt; &lt;Setter Property="TextBox.Margin" Value="5"/&gt; &lt;Setter Property="TextBox.Padding" Value="15 10"/&gt; &lt;Setter Property="TextBox.HorizontalAlignment" Value="Center" /&gt; &lt;Setter Property="TextBox.Width" Value="250" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ButtonBase" &gt; &lt;Style.Setters&gt; &lt;Setter Property="Button.Background" Value="#333" /&gt; &lt;Setter Property="Button.Foreground" Value="#f6f6f6" /&gt; &lt;Setter Property="Button.Margin" Value="5"/&gt; &lt;Setter Property="Button.Padding" Value="50 10"/&gt; &lt;Setter Property="Button.HorizontalAlignment" Value="Center" /&gt; &lt;Setter Property="Button.FontSize" Value="14" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Теперь сверстаем сам экран авторизации - он должен быть видимым:</p>
30 &lt;Border Style="{StaticResource Screen}" Name="LoginScreen" Visibility="Visible"&gt; &lt;StackPanel Style="{StaticResource LoginPanel}"&gt; &lt;TextBlock Text="Login" Style="{StaticResource HeaderBlock}" /&gt; &lt;TextBox Style="{StaticResource TextBoxBase}" Name="LoginBox" /&gt; &lt;PasswordBox Style="{StaticResource TextBoxBase}" Name="PasswordBox"/&gt; &lt;Button Content="Enter" Style="{StaticResource ButtonBase}" Name="LoginButton" Click="LoginButton_Click" IsDefault="True"/&gt; &lt;TextBlock Text="" Name="LoginMessageBlock" Style="{StaticResource WarningBlock}" Visibility="Hidden"/&gt; &lt;/StackPanel&gt; &lt;/Border&gt;<p>Давайте посмотрим, как это выглядит:</p>
30 &lt;Border Style="{StaticResource Screen}" Name="LoginScreen" Visibility="Visible"&gt; &lt;StackPanel Style="{StaticResource LoginPanel}"&gt; &lt;TextBlock Text="Login" Style="{StaticResource HeaderBlock}" /&gt; &lt;TextBox Style="{StaticResource TextBoxBase}" Name="LoginBox" /&gt; &lt;PasswordBox Style="{StaticResource TextBoxBase}" Name="PasswordBox"/&gt; &lt;Button Content="Enter" Style="{StaticResource ButtonBase}" Name="LoginButton" Click="LoginButton_Click" IsDefault="True"/&gt; &lt;TextBlock Text="" Name="LoginMessageBlock" Style="{StaticResource WarningBlock}" Visibility="Hidden"/&gt; &lt;/StackPanel&gt; &lt;/Border&gt;<p>Давайте посмотрим, как это выглядит:</p>
31 <a></a><p>Теперь напишем обработчик для кнопки<em>Enter</em>:</p>
31 <a></a><p>Теперь напишем обработчик для кнопки<em>Enter</em>:</p>
32 //Обработчик нажатия на кнопку Login private void LoginButton_Click(object sender, RoutedEventArgs e) { //Пока используем тестовые данные if(LoginBox.Text == "admin" &amp;&amp; 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; }<p>У вас должны быть экраны с именами<em>ContactsScreen</em>и <em>ChatScreen</em>, чтобы метод<em>Open ()</em>работал корректно. Для этого достаточно создать два пустых элемента<em>Border</em>.</p>
32 //Обработчик нажатия на кнопку Login private void LoginButton_Click(object sender, RoutedEventArgs e) { //Пока используем тестовые данные if(LoginBox.Text == "admin" &amp;&amp; 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; }<p>У вас должны быть экраны с именами<em>ContactsScreen</em>и <em>ChatScreen</em>, чтобы метод<em>Open ()</em>работал корректно. Для этого достаточно создать два пустых элемента<em>Border</em>.</p>
33 <p>Вот как выглядит экран авторизации при вводе неверных данных:</p>
33 <p>Вот как выглядит экран авторизации при вводе неверных данных:</p>
34 <a></a><p>Дальше сверстаем экран с контактами.</p>
34 <a></a><p>Дальше сверстаем экран с контактами.</p>
35 <p>Экран с контактами разделим на два ряда с помощью Grid - заголовок в первом ряду и список контактов во втором. Список - это элемент<em>ListBox</em>, в котором перечислены контакты.</p>
35 <p>Экран с контактами разделим на два ряда с помощью Grid - заголовок в первом ряду и список контактов во втором. Список - это элемент<em>ListBox</em>, в котором перечислены контакты.</p>
36 &lt;Border Name="ContactsScreen" Style="{StaticResource Screen}"&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="50" /&gt; &lt;RowDefinition /&gt; &lt;/Grid.RowDefinitions&gt; &lt;Border Grid.Row="0" Style="{StaticResource HeaderBorder}"&gt; &lt;TextBlock Style="{StaticResource HeaderBlock}" Text="Contacts" VerticalAlignment="Center"/&gt; &lt;/Border&gt; &lt;Border Grid.Row="1"&gt; &lt;ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" Style="{StaticResource ContactsList}" Name="ContactsList" SelectionChanged="ContactsList_SelectionChanged"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;ListBoxItem&gt; &lt;DockPanel LastChildFill="True"&gt; &lt;Image Style="{StaticResource ContactImage}" DockPanel.Dock="Left"&gt;&lt;/Image&gt; &lt;TextBlock Text="{Binding Name}" Style="{StaticResource ContactName}" DockPanel.Dock="Right"/&gt; &lt;/DockPanel&gt; &lt;/ListBoxItem&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/Border&gt; &lt;/Grid&gt; &lt;/Border&gt;<p>Здесь описаны не все элементы списка, а только шаблон для них, который будет использоваться во время привязки данных. Этот шаблон понадобится в готовом приложении, но для удобства вёрстки вы можете подготовить тестовые данные.</p>
36 &lt;Border Name="ContactsScreen" Style="{StaticResource Screen}"&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="50" /&gt; &lt;RowDefinition /&gt; &lt;/Grid.RowDefinitions&gt; &lt;Border Grid.Row="0" Style="{StaticResource HeaderBorder}"&gt; &lt;TextBlock Style="{StaticResource HeaderBlock}" Text="Contacts" VerticalAlignment="Center"/&gt; &lt;/Border&gt; &lt;Border Grid.Row="1"&gt; &lt;ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" Style="{StaticResource ContactsList}" Name="ContactsList" SelectionChanged="ContactsList_SelectionChanged"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;ListBoxItem&gt; &lt;DockPanel LastChildFill="True"&gt; &lt;Image Style="{StaticResource ContactImage}" DockPanel.Dock="Left"&gt;&lt;/Image&gt; &lt;TextBlock Text="{Binding Name}" Style="{StaticResource ContactName}" DockPanel.Dock="Right"/&gt; &lt;/DockPanel&gt; &lt;/ListBoxItem&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/Border&gt; &lt;/Grid&gt; &lt;/Border&gt;<p>Здесь описаны не все элементы списка, а только шаблон для них, который будет использоваться во время привязки данных. Этот шаблон понадобится в готовом приложении, но для удобства вёрстки вы можете подготовить тестовые данные.</p>
37 <p>Зададим стили:</p>
37 <p>Зададим стили:</p>
38 &lt;Style x:Key="ContactsList" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="ListBox.Background" Value="#151515"/&gt; &lt;Setter Property="ListBox.BorderThickness" Value="0"/&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ContactImage" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ContactName" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.HorizontalAlignment" Value="Left"/&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="HeaderBorder"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Background" Value="#222" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Теперь остаётся написать метод, который открывает чат с контактом. Этот метод вызывается, когда пользователь выберет пункт из списка.</p>
38 &lt;Style x:Key="ContactsList" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="ListBox.Background" Value="#151515"/&gt; &lt;Setter Property="ListBox.BorderThickness" Value="0"/&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ContactImage" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="ContactName" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.HorizontalAlignment" Value="Left"/&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="HeaderBorder"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Background" Value="#222" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Теперь остаётся написать метод, который открывает чат с контактом. Этот метод вызывается, когда пользователь выберет пункт из списка.</p>
39 private void ContactsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { //Метод вызывается, когда меняется индекс выделенного элемента //При выделении элемент списка будет подсвечиваться //Чтобы убрать это, мы будем менять индекс на -1 //Чтобы метод не срабатывал повторно, мы проверяем, чтобы индекс был больше или равен 0 if(ContactsList.SelectedIndex &gt;= 0) { //Тут будет код загрузки сообщений из чата //Сбрасываем индекс ContactsList.SelectedIndex = -1; Open(ChatScreen); } }<p>Чат похож на экран с контактами, но немного дополненный:</p>
39 private void ContactsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { //Метод вызывается, когда меняется индекс выделенного элемента //При выделении элемент списка будет подсвечиваться //Чтобы убрать это, мы будем менять индекс на -1 //Чтобы метод не срабатывал повторно, мы проверяем, чтобы индекс был больше или равен 0 if(ContactsList.SelectedIndex &gt;= 0) { //Тут будет код загрузки сообщений из чата //Сбрасываем индекс ContactsList.SelectedIndex = -1; Open(ChatScreen); } }<p>Чат похож на экран с контактами, но немного дополненный:</p>
40 <a></a><p><em>Grid</em>делит экран на три части: заголовок, чат и поле ввода. В заголовке - имя собеседника и кнопка "Назад". В чате выведены сообщения с помощью<em>ListBox</em>, а внизу находятся поле ввода и кнопка отправки.</p>
40 <a></a><p><em>Grid</em>делит экран на три части: заголовок, чат и поле ввода. В заголовке - имя собеседника и кнопка "Назад". В чате выведены сообщения с помощью<em>ListBox</em>, а внизу находятся поле ввода и кнопка отправки.</p>
41 &lt;Border Name="ChatScreen" Style="{StaticResource Screen}"&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="50" /&gt; &lt;RowDefinition /&gt; &lt;RowDefinition Height="50"/&gt; &lt;/Grid.RowDefinitions&gt; &lt;Border Grid.Row="0" Style="{StaticResource HeaderBorder}"&gt; &lt;Grid&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;ColumnDefinition Width="6*" /&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;Button Style="{StaticResource NavButton}" Grid.Column="0" Name="BackButton" Content="←" Click="BackButton_Click"/&gt; &lt;TextBlock Style="{StaticResource HeaderBlock}" Text="" VerticalAlignment="Center" Name="ChatName" Grid.Column="1"/&gt; &lt;/Grid&gt; &lt;/Border&gt; &lt;Border Grid.Row="1"&gt; &lt;ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" Style="{StaticResource ContactsList}" Name="MessagesList" Focusable="False" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;ListBoxItem&gt; &lt;Border Style="{StaticResource MessageBorder}" HorizontalAlignment="{Binding Alignment}"&gt; &lt;StackPanel Orientation="Vertical"&gt; &lt;TextBlock Text="{Binding Text}" Style="{StaticResource MessageText}"/&gt; &lt;TextBlock Text="{Binding Date}" Style="{StaticResource MessageDate}"/&gt; &lt;/StackPanel&gt; &lt;/Border&gt; &lt;/ListBoxItem&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/Border&gt; &lt;Border Grid.Row="2" Style="{StaticResource HeaderBorder}"&gt; &lt;Grid&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="6*" /&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;TextBox Name="MessageBox" Style="{StaticResource MessageBox}" Grid.Column="0"/&gt; &lt;Button Style="{StaticResource NavButton}" Grid.Column="1" Name="SendButton" Content="→" Click="SendButton_Click"/&gt; &lt;/Grid&gt; &lt;/Border&gt; &lt;/Grid&gt; &lt;/Border&gt;<p>Тут, как на экране с контактами, пока нет данных -- только шаблон для их вывода. Немного стилей:</p>
41 &lt;Border Name="ChatScreen" Style="{StaticResource Screen}"&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="50" /&gt; &lt;RowDefinition /&gt; &lt;RowDefinition Height="50"/&gt; &lt;/Grid.RowDefinitions&gt; &lt;Border Grid.Row="0" Style="{StaticResource HeaderBorder}"&gt; &lt;Grid&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;ColumnDefinition Width="6*" /&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;Button Style="{StaticResource NavButton}" Grid.Column="0" Name="BackButton" Content="←" Click="BackButton_Click"/&gt; &lt;TextBlock Style="{StaticResource HeaderBlock}" Text="" VerticalAlignment="Center" Name="ChatName" Grid.Column="1"/&gt; &lt;/Grid&gt; &lt;/Border&gt; &lt;Border Grid.Row="1"&gt; &lt;ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" Style="{StaticResource ContactsList}" Name="MessagesList" Focusable="False" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;ListBoxItem&gt; &lt;Border Style="{StaticResource MessageBorder}" HorizontalAlignment="{Binding Alignment}"&gt; &lt;StackPanel Orientation="Vertical"&gt; &lt;TextBlock Text="{Binding Text}" Style="{StaticResource MessageText}"/&gt; &lt;TextBlock Text="{Binding Date}" Style="{StaticResource MessageDate}"/&gt; &lt;/StackPanel&gt; &lt;/Border&gt; &lt;/ListBoxItem&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/Border&gt; &lt;Border Grid.Row="2" Style="{StaticResource HeaderBorder}"&gt; &lt;Grid&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="6*" /&gt; &lt;ColumnDefinition Width="1*" /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;TextBox Name="MessageBox" Style="{StaticResource MessageBox}" Grid.Column="0"/&gt; &lt;Button Style="{StaticResource NavButton}" Grid.Column="1" Name="SendButton" Content="→" Click="SendButton_Click"/&gt; &lt;/Grid&gt; &lt;/Border&gt; &lt;/Grid&gt; &lt;/Border&gt;<p>Тут, как на экране с контактами, пока нет данных -- только шаблон для их вывода. Немного стилей:</p>
42 &lt;Style x:Key="MessageBorder"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Background" Value="#555" /&gt; &lt;Setter Property="Border.CornerRadius" Value="13" /&gt; &lt;Setter Property="Border.MinWidth" Value="100" /&gt; &lt;Setter Property="Border.MaxWidth" Value="300" /&gt; &lt;Setter Property="Border.Padding" Value="2" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="MessageText" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.TextWrapping" Value="Wrap" /&gt; &lt;Setter Property="TextBlock.Margin" Value="0" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="MessageDate" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.HorizontalAlignment" Value="Right" /&gt; &lt;Setter Property="TextBlock.FontSize" Value="8" /&gt; &lt;Setter Property="TextBlock.Margin" Value="0" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Остаётся только обработать события для кнопок отправки и навигации:</p>
42 &lt;Style x:Key="MessageBorder"&gt; &lt;Style.Setters&gt; &lt;Setter Property="Border.Background" Value="#555" /&gt; &lt;Setter Property="Border.CornerRadius" Value="13" /&gt; &lt;Setter Property="Border.MinWidth" Value="100" /&gt; &lt;Setter Property="Border.MaxWidth" Value="300" /&gt; &lt;Setter Property="Border.Padding" Value="2" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="MessageText" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.TextWrapping" Value="Wrap" /&gt; &lt;Setter Property="TextBlock.Margin" Value="0" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt; &lt;Style x:Key="MessageDate" BasedOn="{StaticResource TextBlockBase}"&gt; &lt;Style.Setters&gt; &lt;Setter Property="TextBlock.HorizontalAlignment" Value="Right" /&gt; &lt;Setter Property="TextBlock.FontSize" Value="8" /&gt; &lt;Setter Property="TextBlock.Margin" Value="0" /&gt; &lt;/Style.Setters&gt; &lt;/Style&gt;<p>Остаётся только обработать события для кнопок отправки и навигации:</p>
43 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); }<p>У нас получился довольно минималистичный дизайн, который вы можете доработать как вам нравится. Дальше нам предстоит написать серверную часть приложения, а потом связать их вместе.</p>
43 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); }<p>У нас получился довольно минималистичный дизайн, который вы можете доработать как вам нравится. Дальше нам предстоит написать серверную часть приложения, а потом связать их вместе.</p>
44 <a>Практический курс: "Веб-вёрстка" Узнать о курсе</a>
44 <a>Практический курс: "Веб-вёрстка" Узнать о курсе</a>