Pull to refresh

Пишем чат для локальной сети, используя C++ Builder. Клиентская часть

Reading time5 min
Views9.4K
Доброго времени суток.

Это продолжение статьи, в котором я расскажу о создании клиента для моего чата.

Клиентской части мною было уделено гораздо больше времени серверной, так как здесь есть вторая важная составляющая — графическая часть.

Дизайн клиента очень простой, даже примитивный. Я не видел смысла создавать меню бар в приложении. На форме размещены 2 панели, одна из них меняет цвет, если клиент подключен к серверу она зеленая, иначе — красная. На следующей панели размещен TabControl. Я перепробовал 5 или 6 вариантов дизайна приложения, и самым удобным нашел использование компонента TabControl. В его вкладки заносятся имена пользователей находящихся в сети, при выборе соответствующей вкладки начинается переписка с этим пользователем(также выводится история сообщений). Сообщения выводятся в компонент Memo, писать сообщения надо в компонент Edit, отправка- по нажатию соответствующей кнопки или клавише Enter.

У клиента также реализовано сворачивание окна в область уведомлений.

void __fastcall TFormMain::DrawItem(TMessage& Msg)
{
     IconDrawItem((LPDRAWITEMSTRUCT)Msg.LParam);
     TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::MyNotify(TMessage& Msg)
{
    POINT MousePos;

    switch(Msg.LParam)
    {
        case WM_RBUTTONUP:
            if (GetCursorPos(&MousePos))
            {
                PopupMenu1->PopupComponent = FormMain;
                SetForegroundWindow(Handle);
                PopupMenu1->Popup(MousePos.x, MousePos.y);
            }
            else
                Show();
            break;
        case WM_LBUTTONDBLCLK:
        Show();

        break;
        default:
            break;
    }
    TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
bool __fastcall TFormMain::TrayMessage(DWORD dwMessage)
{
   NOTIFYICONDATA tnd;
   PSTR pszTip;

   pszTip = TipText();

   tnd.cbSize          = sizeof(NOTIFYICONDATA);
   tnd.hWnd            = Handle;
   tnd.uID             = IDC_MYICON;
   tnd.uFlags          = NIF_MESSAGE | NIF_ICON | NIF_TIP;
   tnd.uCallbackMessage	= MYWM_NOTIFY;

   if (dwMessage == NIM_MODIFY)
    {
        tnd.hIcon		= (HICON)IconHandle();
        if (pszTip)
           lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip));
	    else
        tnd.szTip[0] = '\0';
    }
   else
    {
        tnd.hIcon = NULL;
        tnd.szTip[0] = '\0';
    }

   return (Shell_NotifyIcon(dwMessage, &tnd));
}
//---------------------------------------------------------------------------
HICON __fastcall TFormMain::IconHandle(void)
{
return (Image2->Picture->Icon->Handle);
}

//---------------------------------------------------------------------------
PSTR __fastcall TFormMain::TipText(void)
{
        return ("Office Chat");

}
//---------------------------------------------------------------------------
LRESULT IconDrawItem(LPDRAWITEMSTRUCT lpdi)
{
return 0;
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------


void __fastcall TFormMain::FormDestroy(TObject *Sender)
{
	TrayMessage(NIM_DELETE);
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N1Click(TObject *Sender)
{
Show();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N2Click(TObject *Sender)
{
Application->Terminate();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::FormCloseQuery(TObject *Sender, bool &CanClose)
{
CanClose=false;
FormMain->Hide();
}
//---------------------------------------------------------------------------

При поступлении нового сообщения воспроизводится приятный звук.

За всю работу с сетью отвечает стандартный компонент ClientSocket. Клиент, также как и сервер, принимая сообщения первым делом отделяет от них первые 4 символа. Затем определяется что делать дальше. Все происходит в событии OnRead.

Код 4796 значит что клиенту следует отправить свое имя для «регистрации» на сервере.

7788 — один из самых главных кодов, он ставится в начале входящего сообщения. При этом отделяется имя отправителя и само сообщения. Далее идет проверка на состояние окна клиента. Если открыта вкладка с диалогом отправителя сообщения то сообщение просто добавляется в новую строку Memo, в начале добавляется время поступления сообщения. Если открыта вкладка переписки с другим пользователем, воспроизводится звук и во вкладке с нужным пользователем после имени добавляется надпись "+1". При переходе в эту вкладку надпись убирается. Если окно приложения свернуто то выплывает контекстное меню с двумя вариантами: закрыть меню и перейти к переписке. Меню вызывается на всех компьютерах в одинаковом месте — правом верхнем углу. Для этого определяется разрешение монитора. В любом из случаев сообщение вместе со временем поступления вносится в файл. Для каждого пользователя есть свой файл переписки, из него же берется история переписки.

8714 — значит, что пора обновить список пользователей в TabControl.

void __fastcall TForm1::ClientSocketRead(TObject *Sender,
      TCustomWinSocket *Socket)
{
AnsiString str=Now().CurrentDateTime();
message=Socket->ReceiveText();
AnsiString recieveText=message;
AnsiString Text=recieveText;
if(message.SubString(1,4).AnsiCompare("4796")==0)
{
ClientSocket->Socket->SendText("6141"+myname);
}
else if(message.SubString(1,4).AnsiCompare("7788")==0)
{
        if(TabControl1->Tabs->operator [](TabControl1->TabIndex).AnsiCompare(message.SubString(5,message.Pos(":")-5))==0)
        {
        ListBox->Lines->Add(str+"  "+message.SubString(5,message.Length()));
        file(message.SubString(5,message.Pos(":")-5),message.SubString(message.Pos(":")+1,message.Length()),Now().CurrentDateTime(),"in");
                PlaySound("message.wav",0,SND_ASYNC);
        PopupMenu2->PopupComponent = Form1;
        PopupMenu2->Popup(GetSystemMetrics(SM_CXSCREEN)-200, 20);
        }
        else if(TabControl1->Tabs->operator [](TabControl1->TabIndex).AnsiCompare(message.SubString(5,message.Pos(":")-5))!=0)
        {
        PlaySound("message.wav",0,SND_ASYNC);
        AnsiString im;
        AnsiString mess;
        im=message.SubString(5,message.Pos(":")-5);
        mess=message.SubString(message.Pos(":")+1,message.Length());
        file(im,mess,Now().CurrentDateTime(),"in");
        AnsiString name=message.SubString(5,message.Pos(":")-5);
        active=TabControl1->TabIndex;
        count=TabControl1->Tabs->Count;
        AnsiString n;
        TabControl1->Tabs->Clear();
        for(int i=0;i<count;i++)
        {
        n=m[i];
        if(n.AnsiCompare(name)!=0)
        TabControl1->Tabs->Add(n);
        else if(n.AnsiCompare(name)==0)
        TabControl1->Tabs->Add(n+"+1");

        }
        }
}
else if(message.SubString(1,4).AnsiCompare("8714")==0)
{
TabControl1->Tabs->Clear();
AnsiString  str=message.SubString(5,message.Length());
 const char separator[]=",";
int i=0;
    char *Ptr=NULL;

    Ptr=strtok(str.c_str(),separator);
    while (Ptr)
    {
    //if(myname.AnsiCompare(Ptr)!=0)
    TabControl1->Tabs->Add(Ptr);
    strcpy(m[i],Ptr);

       Ptr=strtok(0,separator);
       i++;
    }

}
}

В одной папке с приложением обязательно должен храниться файл конфигурации( с чисто символическим расширением ".config", потом сделаю нормальный INI-файл). В файле три строчки: имя клиента, ip-адрес сервера и версия приложения. При запуске приложения все это извлекается из файлы и используется по назначению.

Итог


Мне удалось разработать примитивную, но все таки очень работоспособную программу позволяющую поболтать на рабочем месте связать несколько десятков компьютеров в офисе и прекратить общение с помощью телефонных звонков или тем более хождения по офису.

У меня появились кое-какие идеи, которые в будущем будут реализованы, например добавление графического чата, возможности пересылки файлов, может быть голосовой чат. Все это, конечно же надо реализовывать не с помощью билдера 2006 года. Проект, например я уже перенес в Embarcadero RAD Studio XE8, очень уж нужна была версия на мак.

На этом думаю все, самое важное я написал. Очень надеюсь что данная статья будет полезна для начинающих работать в C++ Builder. Все исходники и сама программа тут.

P.S. Первую часть статьи заминусовали, но первый опыт написания статей мне очень понравился. Комментируйте, буду исправлять свои ошибки. Спасибо за уделенное внимание!
Tags:
Hubs:
Total votes 48: ↑16 and ↓32-16
Comments9

Articles