Доброго времени суток.

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

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

Дизайн клиента очень простой, даже примитивный. Я не видел смысла создавать меню бар в приложении. На форме размещены 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. Первую часть статьи заминусовали, но первый опыт написания статей мне очень понравился. Комментируйте, буду исправлять свои ошибки. Спасибо за уделенное внимание!

Комментарии (9)


  1. Dimezis
    05.06.2015 23:47
    +12

    Код ужасен, практическая польза статьи тоже не особо понятна.


    1. altanium Автор
      05.06.2015 23:53
      -3

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


      1. AdmAlexus
        06.06.2015 08:52
        +6

        Понимаете. Если бы вы писали узкоспециализированную программу, для которой нет конкурентов (ну я не знаю, например «Расчет оптимальной толщины куска мяса, для качественной прожарки в условиях Крайнего Севера»), то вас оценили бы. А в случае чата для локалки… Ведь есть множество платных и бесплатных аналогов с большим функционалом. Да и студенты каждый год пишут я думаю множество таких программулек (сам в свое время писал, используя для отправки NET SEND).
        Поэтому, учитывая ваш комментарий ниже, «Пишите про Ардуино».
        Всех благ и удачи.


        1. altanium Автор
          06.06.2015 08:58

          Согласен с вами, особенно с тем что в интернете найдется 1000 похожих программ которые в 1000 раз лучше моей. Но если бы я залил статью о том как я ищу в интернете чат для локальной сети, написаный на билдере, это восприняли бы еще хуже =) И это и был проект для студенческой олимпиады, который достаточно высоко оценили( наверное у остальных просто все было даже еще хуже чем у меня).


          1. Mixim333
            06.06.2015 20:39

            Простите, но у нас эта тема была даже не «для студенческой олимпиады», а для курсовой по Сетям ЭВМ на 3 курсе (у самого была тема «Передача видеопотока по сети»)!


      1. r0b1n
        06.06.2015 18:07
        +2

        Простите, но помогла бы научиться плохому. Ну или на примере вашего когда можно рассказывать как делать не надо. Лучше попробуйте вместо кодинга продумать структуру программы, поискать какие патерны/подходы можно использовать в различных частях приложения и на какие компоненты его разбить.


  1. Ivanhoe
    05.06.2015 23:52
    +7

    Предлагаю, как минимум, не угонять автору карму в минуса. Человек, очевидно, старается :)


    1. altanium Автор
      05.06.2015 23:56
      +3

      Я постараюсь писать качественные статьи по Arduino


  1. farcaller
    06.06.2015 15:32

    Как насчет хотя бы через uncrustify прогнать исходники? Форматирование вообще никакое же. Код лучше оно, естественно, не сделает.