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

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

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

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

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

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

      +12
      Код ужасен, практическая польза статьи тоже не особо понятна.
        –3
        Меня уже поругали, буду исправлять. Практическая польза я считаю есть, лично мне такая статья помогла бы когда я программу разрабатывал.
          +6
          Понимаете. Если бы вы писали узкоспециализированную программу, для которой нет конкурентов (ну я не знаю, например «Расчет оптимальной толщины куска мяса, для качественной прожарки в условиях Крайнего Севера»), то вас оценили бы. А в случае чата для локалки… Ведь есть множество платных и бесплатных аналогов с большим функционалом. Да и студенты каждый год пишут я думаю множество таких программулек (сам в свое время писал, используя для отправки NET SEND).
          Поэтому, учитывая ваш комментарий ниже, «Пишите про Ардуино».
          Всех благ и удачи.
            0
            Согласен с вами, особенно с тем что в интернете найдется 1000 похожих программ которые в 1000 раз лучше моей. Но если бы я залил статью о том как я ищу в интернете чат для локальной сети, написаный на билдере, это восприняли бы еще хуже =) И это и был проект для студенческой олимпиады, который достаточно высоко оценили( наверное у остальных просто все было даже еще хуже чем у меня).
            • НЛО прилетело и опубликовало эту надпись здесь
            +2
            Простите, но помогла бы научиться плохому. Ну или на примере вашего когда можно рассказывать как делать не надо. Лучше попробуйте вместо кодинга продумать структуру программы, поискать какие патерны/подходы можно использовать в различных частях приложения и на какие компоненты его разбить.
          +7
          Предлагаю, как минимум, не угонять автору карму в минуса. Человек, очевидно, старается :)
            +3
            Я постараюсь писать качественные статьи по Arduino
            0
            Как насчет хотя бы через uncrustify прогнать исходники? Форматирование вообще никакое же. Код лучше оно, естественно, не сделает.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое