Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторный_практикум.doc
Скачиваний:
74
Добавлен:
15.11.2019
Размер:
45.35 Mб
Скачать

5. Создание приложения Windows Forms: клиент чата на wcf

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

Создаём новый проект в текущем решении (LWP16). Для этого выполним следующие действия: Файл -> Создать -> Проект. В окне открывшемся окне «Создать проект», в поле Решение, выберем: Добавить в решение. В качестве проекта выберем Приложение Windows Forms. Имя будет: LWP16-ChatClient. Жмём ОК.

Сразу же добавим необходимые библиотеки. Должен быть выбрать текущий проект в обозревателе решений (LWP16-ChatClient). Выполним Проект -> Добавить ссылку... -> в окне Добавить ссылку переходим на вкладку .NET и ищем System.ServiceModel. Если ОС на которой запущена среда разработки Windows 7, добавим также ещё одну библиотеку: Microsoft Speech Object Library (вкладка COM):

Рис. 5. 2. Добавить ссылку: добавляем новую ссылку на библиотеку Microsoft Speech Object Library

Переименуем форму проекта клиентского приложений в обозревателе решений (ПКМ на иконке формы -> Переименовать). Новое имя формы будет LWP16MainClient. После переименования, автоматически изменится свойство (Name) формы.

Теперь изменим свойства формы LWP16MainClient:

Text

изменим с Form1 на Чат на WCF (C#) :: Клиент

^ Поменяем заголовок формы (то, что отображается в шапке приложения слева).

MaximizeBox

изменим с True на False

^ Уберём кнопку Развернуть.

Icon

изменим изображение (иконку) приложения

^ Необходим файл значка *.ico.

FormBorderStyle

> изменим с Sizable на FixedDialog

^ Сделаем окно «неизменяем» по размерам.

Size

изменим со значений 290; 290 на 300; 125

^ Поменяем размер формы.

Расставим элементы с панели инструментов как показано на рисунке ниже:

Рис. 5. 1. Расстановка элементов на форме приложения-клиента

Здесь у нас в первой группе (GroupBox) «Пользовательские данные»: один Label, один TextBox и Button.

Свойства элементов следующие:

GroupBox:

(Name):

GB_UserDetails

Text:

Пользовательские данные

Label:

(Name):

L_LoginInfo

Text:

Введите имя для входа и нажмите на «Войти в чат»

TextBox:

(Name):

TB_UserName

Button:

(Name):

B_Login

Text:

Войти в чат

Во второй группе элементов один GroupBox и один ListBox:

GroupBox:

(Name):

GB_UserList

Text:

Пользователи в сети

ListBox:

(Name):

LB_Users

Последняя группа содержит один RichTextBox, TextBox и две кнопки.

GroupBox:

(Name):

GB_MessageWindow

Text:

Сообщения чата

RichTextBox:

(Name):

RTB_Messages

ReadOnly:

True

Multiline

True

TextBox:

(Name):

TB_SendMessage

Button:

(Name):

B_Send

Text:

Отправить

Button:

(Name):

B_WakeUp

Text:

!

Перейдём к редактированию кода формы. В начале файла с кодом для формы LWP16MainClient.cs объявим:

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Runtime.InteropServices;

using SpeechLib; // Для работы имитации голоса (в Windows XP не использовать)

Найдём:

namespace LWP16_ChatClient

{

Добавим после:

[ServiceContract(CallbackContract = typeof(ILWP16Service))]

public interface ILWP16Service // Интерфейс подключения к службе сервера

{

[OperationContract(IsOneWay = true)] // Операция не возвращает ответовное сообщение

void Join(string memberName); // Объявление метода присоединения к чату (по имени)

[OperationContract(IsOneWay = true)]

void SendMessage(string memberName, string message); // Объявление метода отсылки сообщения (по имени и тексту)

[OperationContract(IsOneWay = true)]

void WakeUp(string memberName, string wakeup); // Объявление метода отсылки сообщения (по имени и тексту)

[OperationContract(IsOneWay = true)]

void Leave(string memberName); // Объявление метода выхода из чата (по имени)

[OperationContract(IsOneWay = true)]

void ImageFF(string memberName, Bitmap image); // Объявление метода выхода из чата (по имени)

}

public interface ILWP16Channel : ILWP16Service, IClientChannel

{

}

Найдём:

public partial class LWP16MainClient : Form

{

Заменим на:

public partial class LWP16MainClient : Form, ILWP16Service

{

[DllImport("kernel32.dll")]

public static extern bool Beep(int BeepFreq, int BeepDuration);

private delegate void UserJoined(string name);

private delegate void UserSendMessage(string name, string message);

private delegate void UserWakeUp(string name, string wakeup);

private delegate void UserLeft(string name);

private static event UserJoined NewJoin; // Экземпляр события присоединения к чату через делегат

private static event UserSendMessage MessageSent; // Экземпляр события отсылки сообщения через делегат

private static event UserWakeUp NewWakeUp; // Экземпляр события сообщения типа: "разбудить чат"

private static event UserLeft RemoveUser; // Экземпляр события выхода из чата через делегат

private string userName; // Переменная имени пользователя в чате

private ILWP16Channel channel; // Экземпляр интерфейса чата для канала

// class ServiceModel.DuplexChannelFactory<TChannel>

private DuplexChannelFactory<ILWP16Channel> factory; // Объект средства приёма и передаче "дуплексных" сообщений по каналам в обе стороны

Метод LWP16MainClient() измени так:

public LWP16MainClient()

{

InitializeComponent();

this.AcceptButton = B_Login; // Привязываем событие Нажатия Enter с кнопкой "Войти в чат"

}

После добавим:

public LWP16MainClient(string userName)

{

this.userName = userName;

}

// Метод присоединения к чату (по имени)

void LWP16Client_NewJoin(string name)

{

RTB_Messages.AppendText("\r\n");

RTB_Messages.AppendText(name + " присоединился: [" + DateTime.Now.ToString() + "]"); // Добавляем в RichTextBox строчку c именем и датой входа пользователя

LB_Users.Items.Add(name); // Добавляем нового пользователя в ListBox

}

// Метод отсылки сообщения (по имени и тексту)

void LWP16Client_MessageSent(string name, string message)

{

if (!LB_Users.Items.Contains(name)) // Если имени нет в ListBox при получении сообщения в RichTextBox

{

LB_Users.Items.Add(name); // Добавляет нового пользователя в ListBox

}

RTB_Messages.AppendText("\r\n");

RTB_Messages.AppendText(name + " говорит: " + message + " [" + DateTime.Now.ToString() + "]"); // Добавляем в RichTextBox строчку с именем и сообщением

if (message == "WakeUp") { Beep(500, 100); }

}

// Метод присоединения к чату (по имени)

void LWP16Client_WakeUp(string name, string wakeup)

{

if (!LB_Users.Items.Contains(name)) // Если имени нет в ListBox при получении сообщения в RichTextBox

{

LB_Users.Items.Add(name); // Добавляет нового пользователя в ListBox

}

RTB_Messages.AppendText("\r\n");

RTB_Messages.AppendText(name + " попытался разбудить чат: [" + DateTime.Now.ToString() + "]"); // Добавляем в RichTextBox строчку c именем и датой входа пользователя

if (wakeup == "WakeUp")

{

Beep(500, 100);

Beep(500, 100);

SpVoice voice = new SpVoice();

voice.Speak("Wake Up Mate", SpeechVoiceSpeakFlags.SVSFDefault);

}

}

// Метод выхода из чата (по имени)

void LWP16Client_RemoveUser(string name)

{

try

{

RTB_Messages.AppendText("\r\n");

RTB_Messages.AppendText(name + " вышел: [" + DateTime.Now.ToString() + "]"); // Добавляем в RichTextbox строчку с именем и датой выхода пользователя

LB_Users.Items.Remove(name); // Удаляем по имени из ListBox

}

catch (Exception ex)

{

System.Diagnostics.Trace.WriteLine(ex.ToString());

}

}

void Online(object sender, EventArgs e)

{

RTB_Messages.AppendText("\r\nВ сети: " + this.userName);

}

void Offline(object sender, EventArgs e)

{

RTB_Messages.AppendText("\r\nНе в сети: " + this.userName);

}

#region ILWP16Service Основные методы

public void Join(string memberName)

{

if (NewJoin != null)

{

NewJoin(memberName);

}

}

public void SendMessage(string memberName, string message)

{

if (MessageSent != null)

{

MessageSent(memberName, message);

}

}

public void WakeUp(string memberName, string wakeup)

{

if (NewWakeUp != null)

{

NewWakeUp(memberName, wakeup);

}

}

public new void Leave(string memberName)

{

if (RemoveUser != null)

{

RemoveUser(memberName);

}

}

#endregion

Перейдём к событиям формы. Для кнопки «Войти в чат» событие Click будет содержать код:

private void B_Login_Click(object sender, EventArgs e)

{

if (!string.IsNullOrEmpty(TB_UserName.Text.Trim()))

{

try

{

NewJoin += new UserJoined(LWP16Client_NewJoin); // Переопределяем вызов метода подключения к чату через экзмепляр события

MessageSent += new UserSendMessage(LWP16Client_MessageSent); // Переопределяем вызов метода отсылки сообщения в чат через экзмепляр события

NewWakeUp += new UserWakeUp(LWP16Client_WakeUp); // Переопределяем вызов метода "разбудить чат" в чат через экзмепляр события

RemoveUser += new UserLeft(LWP16Client_RemoveUser); // Переопределяем вызов метода выхода из чата через экзмепляр события

channel = null;

this.userName = TB_UserName.Text.Trim(); // Удаляем пробелы из имени пользователя

// class ServiceModel.InstanceContext

InstanceContext context = new InstanceContext(new LWP16MainClient(TB_UserName.Text.Trim()));

factory = new DuplexChannelFactory<ILWP16Channel>(context, "ChatEndPoint"); // Получаем данные из app.config и передаём данные duplex-каналу

channel = factory.CreateChannel(); // Создаём канал и передаём его экземпляру интерфейса чата

// class ServiceModel.IOnlineStatus

IOnlineStatus status = channel.GetProperty<IOnlineStatus>(); // Определяем экземпляр для индикации доступности объекта по каналу

status.Offline += new EventHandler(Offline); // Вызов метода Offline (если в чате больше никого)

status.Online += new EventHandler(Online); // Вызов метода Online (если в чате больше одного пользователя)

channel.Open(); // Открываем канал

channel.Join(this.userName); // Вызываем метод Join() с текущим именем пользователя введённым в TB_Username

GB_MessageWindow.Enabled = true; // Включаем группу "Сообщения чата"

GB_UserList.Enabled = true; // Включаем группу "Список пользователей"

GB_UserDetails.Enabled = false; // Гасим группу "Данные для входа"

this.AcceptButton = B_Send; // Enter = "Отослать"

RTB_Messages.AppendText("*****************************ДОБРО ПОЖАЛОВАТЬ В ЧАТ*****************************\r\n");

TB_SendMessage.Select();

TB_SendMessage.Focus();

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString());

}

}

}

События Click кнопок «Отправить» и «!»:

private void B_Send_Click(object sender, EventArgs e)

{

channel.SendMessage(this.userName, TB_SendMessage.Text.Trim()); // Вызываем метод SendMessage() и отсылаем имя пользователя и сообщение

TB_SendMessage.Clear(); // Очищаем TB_SendMessage и далее передаём фокус на элемент и делаем его активным

TB_SendMessage.Select();

TB_SendMessage.Focus();

}

private void B_WakeUp_Click(object sender, EventArgs e)

{

channel.WakeUp(this.userName, "WakeUp"); // Вызываем метод WakeUp() и отсылаем имя пользователя и текст "WakeUp"

TB_SendMessage.Clear(); // Очищаем TB_SendMessage и далее передаём фокус на элемент и делаем его активным

TB_SendMessage.Select();

TB_SendMessage.Focus();

}

Событие FormClosing формы LWP16MainClient:

private void LWP16MainClient_FormClosing(object sender, FormClosingEventArgs e)

{

try

{

if (channel != null)

{

channel.Leave(this.userName); // Если закрываем форму, вызваем метод Leave() и удаляем пользователя

channel.Close(); // Закрываем канал между сервером и клиентом

}

if (factory != null)

{

factory.Close(); // Закрываем duplex-канал

}

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString());

}

}

Теперь разъясним принцип работы всего того что было тут наворочено. После запуска приложения-сервера, происходит инициализация канала по определённому адресу. Был выбран локальный адрес и порт 5433, а также абсолютное имя пространства имён LWP16_ChatServer для адресации по этому порту. Имя для адресации в строке конфигурации клиента:

<endpoint name="ChatEndPoint" address="net.p2p://LWP16-Сhat/LWP16_ChatServer"

И...

<custom address="net.tcp://localhost:5433/LWP16_ChatServer"

Может быть любым, но! Главное чтобы оно совпадало с серверной строкой адреса (в файле конфигурации приложения-сервера). То есть, запущенное приложение-клиент должно содержать в файле конфигурации этот абсолютный адрес.

После ввода имени, становится доступной кнопка «Войти в чат». После нажатия на кнопку, в приложении-клиенте происходит вызов (через делегат и событие) метода интерфейса: Join(string memberName), содержащее переданное из текстового поля строку с именем. И далее выполняется: LWP16Client_NewJoin(string name), где в RichTextBox добавляется запись о присоединении пользователя с таким-то именем к чату и временем присоединения. Имя также добавляется в ListBox. Если пользователь в уже не один, срабатывает метод Online(), которые сразу же оповещает и пользователя и другого пользователя о том, что они «в сети». Метод срабатывает в том случае, если в сети больше одного пользователя, после нажатия кнопки «Войти в чат» пользователя ещё не вошедшего в чат. Вызывает событие после получения по каналу «индикатора события доступности»:

IOnlineStatus status = channel.GetProperty<IOnlineStatus>();

Подключение третьего пользователя не вызывает срабатывание метода у двух предыдущих, но «в сети» отображается у нового пользователя, которому выдаётся оповещение о том что в канале он не один. Также любое подключение вызывает у всех клиентов срабатывание события NewJoin и следовательно Join(<имя подключившегося>).

Сообщение отсылается по такому же событийному механизму, но содержит также текст сообщения, которое пользователь набрал у себя в клиенте. Отправка идёт после нажатия кнопки «Отправить». В ListBox новый пользователи чата добавляются лишь по факту совершения события и появления записи с именем в RichTextBox, потом если в сети один клиент, то об имени уже находящегося в канале пользователя новый клиент узнает только после совершения тем пользователем действий.

Аналогичным образом работает событие WakeUp, которое передаёт чату имя пользователя нажавшего кнопку «!» и текст «WakeUp», который не отображается в чате. После получения такого сообщения, все клиенты издают системный звук (чтобы разбудить тех кто находится в чате). А также, если ОС на которой запущен клиент это Windows Vista или Windows 7, то приложение проговаривает заранее заготовленную фразу. Также системный звук раздаётся, если написать в чат непосредственно слово WakeUp.

Выход из чата (через закрытие формы и вызов события FormClosing) тоже выдаёт оповещения, а также происходит удаление имени вышедшего из ListBox всех клиентов.

Наше приложение-клиент готово. Можно компилировать и проверять работоспособность.