注意:いちおうサービスパック2まで入れておいたほうがいいかも
こういうかんじのを、はじめの目標にします。
機能はこれだけです。 でははじめましょう。
起動したらすぐ適当な名前でプロジェクトを保存します。
実際の動作はあとまわしで、見た目だけ先につくってしまいます。 とりあえずいりそうなのは、
ぐらいかとおもいます。
サーバー接続ボタンをはりつけます。Buttonを選んではりつけてください。 Button1という名前になるはずです。
ログ表示欄をはりつけます。Memoを選んではりつけてください。 Memo1という名前になるはずです。
これで見た目だけ完成です。F9を押すとかってに実行ファイルを生成して実行します。
サーバーを起動して接続ボタンをおしてみましょう。
サーバーに接続されません。 ボタンを押したときの動作をまだ書いてなかったので、しょうがないです。これからです。
ボタンを押したときの動作を書きましょう。 ボタンを押したらサーバーに接続したいわけです。 サーバーに接続するにはどうしたらいいでしょうか? つぎのような手順をふみます。
「ソケットを開く」のあたりは、ClientSocketという部品が用意されてるので、 これをつかいます。
ボタンとちがって直感的にわかりにくいかもしれませんが、 TClientSocketは、通信ソケットを扱うための部品です (ソケットというのはコンセントのさしこみ口のことです)。 ボタンやメモをはりつけたときと同じようにはりつけます。 ツールパレットのIntenetタブからClientSocketを選んではりつけてください。 ClientSocket1という名前になるはずです。
フォームにある接続ボタン(Button1)をダブルクリックすると、 Button1Clickという関数が自動的に作成されます。 この関数は、Button1のOnButtonClickイベントが起こったときに実行されます。 このことは、左下のオブジェクトインスペクタで確認できます。
オブジェクトインスペクタのイベントタブをクリックすると、 Button1のOnClickイベントのところにButton1Clickと書いてあります。
Button1Click関数の中身は、自分で書きます。 「サーバーに接続する」という内容のことを書けばいいです。
ここでは
procedure TForm1.Button1Click(Sender: TObject); begin ConnectToServer; end;
のようにとりあえず書いておいて、 あとでこのConnectToServer関数を書きたすことにします。
どうやって書き足すかというと、
先頭の宣言のところで
procedure ConnectToNapServer;
のように宣言しておいて、
実装のところで
procedure TForm1.ConnectToNapServer; begin ClientSocket1.Host:='127.0.0.1'; ClientSocket1.Port:=8888; ClientSocket1.Open;//ソケット接続開始 end;
このように書きます。
ここまでで、F9をおしてプログラムを実行してみましょう。
サーバーが127.0.0.1:8888で動いているときに、
接続ボタン(Button1)をおすと、サーバーに接続できるはずです。
といっても、接続できたかどうか確認する方法がありません(FWソフトとかでみれるけど)
接続できたらログ窓(Memo1)に「接続できました」とか表示したいですね。
「接続できたら」「ログに表示」したいわけですが、 「接続できたら」の部分を判断するにはにはTClientSocket.OnConnectイベントを使います。 これはソケットが接続先のサーバーに接続できたときに起きるイベントです。
左上のオブジェクトツリーでClientSocket1を選択し、左下のオブジェクトインスペクタの イベントタブの、OnConnectの項目欄をダブルクリックすると、Delphiが 自動的にOnConnectイベントに対応する関数を宣言&実装してくれます。
生成された関数に次のように書き足せば、サーバーlocalhost:8888に接続できたとき、 Memo1に「接続できました」と表示されます。
procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); begin Memo1.Lines.Add('接続できました'); end;
F9を押してプログラムを実行してみてください。こんどは接続ボタンを押してサーバーに接続すると、ログ窓に'接続できました'と出たとおもいます。 プログラムを終了すれば自動的にサーバーから切断できます。
ここまでのコードをのせておきます。
つぎに、サーバーにNapコマンドを送って、ログイン手続きができるようにします。
すこしむずかしくなります。
サーバーにログインするときは、つぎのような文字列をサーバーに送ります。
(レイアウトのため2行で書きましたが、実際は1行で書きます)
<ユーザー名> <パスワード> <ファイル転送用ポート> "<クライアント名>" <回線種類> [<クライアントバージョン番号>]
ただし、この文字列の前に、4バイト分のタグをつけます。
タグの前半2バイトは、文字列の長さをあらわす数字をいれます。
同様に後半2バイトは、Napコマンドの番号(この場合はログイン要求なので2)をいれます。
(それぞれ16進数表示で1の位を1バイト目に、16の位を2バイト目に書きます。)
ということで、けっきょくサーバーにログイン要求のためにおくるメッセージは、例えば
ユーザー名=user1, パスワード=pass, ファイル転送用ポート=6699,
クライアント名=NapChat, 回線種類=8(DSL), クライアントバージョン番号=なし
とすると、
#11#1#2#0user1 pass 6699 "NapChat" 8
のようになります。
では、送信部分の関数を書いてみましょう。
Napコマンドの送信部分は、SendNapCommandという名前の手続きにします。 引数にはソケット、コマンドID、送信データの3つをとると都合がいいです。 さっきつかったClientSocket1Connect関数を書き換えて、 SendNapCommandを実行するようにします。
procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); begin Memo1.Lines.Add('接続できました'); SendNapCommand(Socket,2,'user1 pass 6699 "NapChat" 8');//この行を追加 end;
次にSendNapCommandの中身を書きます。
宣言 procedure SendNapCommand(Socket: TCustomWinSocket; id: Integer; data: String);
実装 procedure TForm1.SendNapCommand(Socket: TCustomWinSocket; id: Integer; data: String); var msg: String; //msg=タグ(4バイト)+文字列 sentbytes: Integer;//SendTextで実際に送られたバイト数 begin msg:=' '+data; msg[1]:=Chr(Length(data) mod 256);//1バイト目=16進数1の位 msg[2]:=Chr(Length(data) div 256);//2バイト目=16進数16の位 msg[3]:=Chr(id mod 256); //3バイト目=16進数1の位 msg[4]:=Chr(id div 256); //4バイト目=16進数16の位 while msg<>'' do //msgが完全に送信されるまでSendTextを繰り返す begin sentbytes:=Socket.SendText(msg);//msgを送信。全部送れるとはかぎらない Delete(msg,1,sentbytes); //送れた分だけmsgからけずる end; Memo1.Lines.Add('送信 ['+IntToStr(id)+'] "'+data+'"'); end;
これでログイン要求をサーバーに送れるようになりました。 サーバーが満員でなければ、きっと要求を受け入れてくれるでしょう。
要求が受け入れられたことを知るには?
サーバーは要求を受け入れたとき、Napコマンドの3番で応答してきます。
この許可応答を受信できなければいけないですね。
さらに、受信できたらそのことがわかるように、
ログに「ログインできました」とか表示したいです。
「許可応答を受信したら」「ログに表示」です。
「許可応答を受信したら」の部分には、
TClientSocket.OnReadイベントを使います。
オブジェクトインスペクタで、ClientSocket1のOnReadイベントの空欄を
ダブルクリックして、イベントに対応する手続きを自動的に生成します。
手続きの名前はClientSocket1Readという名前になったとおもいます。
ソケットが受信した許可応答を取り出す関数をRecvNapCommand、
引数はソケットだけとして、
ClientSocket1Read手続きの中に次のように書いておきます。
procedure TForm1.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket); begin RecvNapCommand(Socket); end;
それからRecvNapCommandの中身をきめます。
宣言 procedure RecvNapCommand(Socket: TCustomWinSocket);
実装 procedure TForm1.RecvNapCommand(Socket: TCustomWinSocket); var id,len: Integer; data: String; msg: PString; begin if Socket.Data=nil then //はじめての受信 begin New(msg); //受信データの保存用領域を確保する Socket.Data:=msg; //Socket.Dataに保存領域のアドレスを入れる end else //2回目以降の受信 msg:=Socket.Data; //msgに保存領域のアドレスを入れる msg^:=msg^+Socket.ReceiveText; //ソケットから受信データを受けとる while Length(msg^)>=4 do begin len:=Ord(msg^[1])+Ord(msg^[2])*256;//データ文字列の長さ id :=Ord(msg^[3])+Ord(msg^[4])*256;//コマンドID if Length(msg^)<4+len then break; //コマンドの長さが足りなかったら終了 data:=Copy(msg^,5,len); //データ文字列 Delete(msg^,1,4+len); //msgから処理ずみのデータを消す Memo1.Lines.Add('受信 ['+IntToStr(id)+'] "'+data+'"'); end; end;
ここで、New手続きをつかって受信用データを置いておくためのメモリを確保していますが、
New手続きで確保したメモリは、いらなくなったらDispose手続きで開放しないといけません。
ここではForm1を閉じたときに開放することにします。
オブジェクトインスペクタでForm1のDestroyイベントの空欄をダブルクリックし、 Form1Destroy手続きを自動的に生成します。 そして、メモリを開放するためのコードをかきます。
procedure TForm1.FormDestroy(Sender: TObject); var msg: PString; begin msg:=ClientSocket1.Socket.Data; if msg<>nil then Dispose(msg);//受信用に確保していたメモリを開放 end;
F9を押して実行してみてください。Button1を押すと、 サーバーに接続して、なにかいろいろメッセージが返されてるのがわかるとおもいます。
これでほとんどできあがりですが、ログ表示欄をもうすこしみやすくしたいですね。 もうすこしMemo1をひろげてみます。あと、縦スクロールバーもほしいです。
まず、ログのメッセージがみにくいので、Memo1のサイズを大きくします。 フォームでMemo1を選択すると、Memo1のふちのところに黒い点が8コついてるとおもいます。 これをドラッグすると、Memo1のサイズをかえることができます。
つぎに、縦スクロールバーをつけます。
オブジェクトインスペクタでプロパティタブをクリックし、
ScrollBarsプロパティの項目をssNone(スクロールバーなし)から
ssVertical(縦スクロールバーつき)に変更すればOKです。
ログがかなりみやすくなりました。
さいごに、もう一箇所だけ修正します。
実行中に、もっとログ画面(Memo1)を広げようとして、
フォームのサイズを大きくしても、ログ画面が大きくなってくれません。
これでは不便なので、フォームにあわせてMemo1のサイズを変えられるようにします。
フォームにあわせてMemo1のサイズを変えられるようにするには、
Anchorsプロパティを変更します。
いま、Anchors=[akLeft,akTop]となっているとおもいます。
これをAnchors=[akLeft,akTop,akRight,akBottom]にかえます。
これで実行中にログ画面を広げられるようになりました。 ログインだけできるクライアントのできあがりです。
ここまでのコードをのせておきます。
ログインだけできるクライアントでは、任意のコマンドを受信して、Memo1に表示できました。 しかし、こちらから送信しているコマンドは2番コマンドだけでした。 実行時にコマンドを入力して、任意のコマンドを送れるクライアントをつくりたいです。 右の絵のようなのを目標にします。 あたらしい機能は、
これだけです。
右の絵のように、Memo1の下をあけて、そこにあたらしいMemoをはりつけてください。 Memo2という名前になるとおもいます。これをコマンド入力欄にします。
オブジェクトインスペクタで、Memo2のプロパティを、
に設定します。
オブジェクトインスペクタで、Memo2のOnKeyDownイベントの項目をダブルクリックし、 対応する手続きMemo2KeyDownを自動的に宣言します。
中身をつぎのように書きます。
procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var str,data: String; id: Integer; begin if Key<>VK_Return then exit; //Enterキーを押したときだけ処理 str:=TrimRight(Memo2.Text)+' '; //入力の右側に空白・制御文字があれば除去 if Length(str)<2 then exit; //空のメッセージは送信しない if str[1]<>'/' then exit; //先頭が'/'の入力だけ処理 id:=StrToIntDef(Copy(str,2,Pos(' ',str)-2),-1); //strからidをとりだす data:=Copy(str,Pos(' ',str)+1,Length(str)-Pos(' ',str)-1); //dataをとりだす SendNapCommand(ClientSocket1.Socket,id,data); Memo2.Clear; end;
実行して、いろいろなコマンドを送ってみてください。 たとえば400番コマンド(チャンネルに入室)を送ると、 右の絵のようにいくつかのコマンドが返ってきます。
任意のコマンドを送れるクライアントが完成しました。
IMのやりとりや管理用コマンドの送信にはこのクライアントで十分ですし、
チャンネルでのチャットやファイル参照も可能です。
検索はちょっときついかもしれませんが、できないことはありません。
ここまでのコードをのせておきます。
任意のコマンドを送れるクライアントでもチャットはできますが、
あきらかに不便です。複数の部屋に入っていると、各部屋の会話が
ひとつの画面の中で混ざって混乱するし、
だれが入室しているのかもわかりにくいです。
そこで、各部屋ごとにチャット窓をひらき、
チャンネルメッセージの入力・表示や入室メンバーの一覧ができるようにしたいです。
右の絵のようなのを目標にします。
あたらしい機能は、つぎのとおりです。
タブをクリックしてページを切り替えられるコントロールです。 実行時にページを作成して、ページにチャット窓をはりつけるために使います。 PageControlコンポーネントはコンポーネントパレットのWin32タブにあります。 はりつけたオブジェクトは、PageControl1という名前になるはずです。
つぎに、オブジェクトツリーかフォーム上のPageControl1を右クリックして、 でてきたメニューから「ページ新規作成(W)」を選択してクリックします。 すると、PageControl1にあたらしいページが追加されます。 このページの名前はTabSheet1という名前になるはずです。 オブジェクトツリー上で、Memo1をTabSheet1にドラッグ&ドロップ してみてください。Memo1がTabSheet1の上に移動します。 同様にして、Memo2,Button1もTabSheet1の上に移動します。 PageControl1は、フォームいっぱいにひろげます。 PageControl1のAlignプロパティをalClientにしてください。 Memo1,Memo2,Button1も、てきとうに大きさや位置を調節します。
PageControl1のページにはりつけるチャット窓です。 メニュー→ファイル→新規作成→フレームを選択してクリックし、 フレームを作成します。このフレームはひとつのユニットとして扱われます。 ユニット名はUnit2となっているはずです。
作成したフレームの名前をChannelFrameに変更します。 このフレームの上に、チャット窓用のコンポーネントをいろいろのせます。
ListView1についてはつぎのセクションで説明します。
メンバー一覧を表示する欄です。ListViewコンポーネントを使います。
ListViewコンポーネントはコンポーネントパレットのWin32タブにあります。
オブジェクトツリーのListView1を右クリックし、「カラムの設定(U)...」をえらびます。 「ListView1.Columnsの編集」ウィンドウがでてくるので、 ここでカラムを3つ追加し、各カラムのCaptionを'Nick','Files','Line'とします。
これでチャット窓の形ができました。 つぎに、このチャット窓をよびだしたり、 メッセージを表示したりするところのコードを追加していきましょう。
まず、チャット部屋に入室するあたりの処理を書きます。 405番(入室許可)コマンドをサーバーから受信したときに、 そのチャンネル名でチャット窓をひらいて手前に表示します。
あとあとの拡張のため、受信したコマンドごとの処理をする手続きをひとつ作ります。
ProcessCommandという名前にしましょう。
引数はソケット、コマンド番号、コマンドの中身の3つでいいとおもいます。
RecvNapCommandの最後に次の一行を追加します。
ProcessCommand(Socket,id,data);
ProcessCommandはあとで宣言・実装します。
ProcessCommandをつぎのように宣言します。
宣言
procedure ProcessCommand(Socket: TCustomWinSocket; id: Integer; data: String);
つぎに実装します。このとき、さっき作成したChannelFrameをつかうので、 Unit1のinterface部のusesにUnit2を追加します。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ScktComp, ComCtrls, Unit2;
実装
procedure TForm1.ProcessCommand(Socket: TCustomWinSocket; id: Integer; data: String); var room: TChannelFrame; sh: TTabSheet; begin case id of 405: //入室許可 <channel> begin sh:=TTabSheet.Create(PageControl1); //ページ(チャット窓)の新規作成 sh.PageControl:=PageControl1; //PageControl1にページを追加 room:=TChannelFrame.Create(sh); //チャンネルフレームの新規作成 room.Parent:=sh; //ページにチャンネルフレームを貼り付け room.Align:=alClient; //フレームをページいっぱいにひろげる sh.Caption:=data; //ページのCaptionをチャンネル名にする PageControl1.ActivePage:=sh; //ページを前面に表示 end; end; end;
実行します。コマンド入力欄から、チャンネル入室コマンドを送信してみてください。
入室許可コマンドが受信されると、PageControl1に新しいページが追加され、 アクティブになります。また、ページのタブの文字列には、 入力したチャンネル名が入ります。
ここまでのコードをのせておきます。
右の絵のように、400番をおくるといくつかのメッセージが返ってきます。
このほかに、チャンネル関係のコマンドとして
の2つもチャットに必要だとおもいます。 くわしくはnap.txtをみてください。
403,410,824番コマンドがきたら、チャット窓にメッセージを表示したいし、 406,407,408番コマンドがきたらチャット窓のメンバー表示欄でメンバーを追加・削除したいです。
そこで、各コマンドごとの処理を、順番にProcessCommandに書き足していきましょう。
まず、メッセージの表示です。
403番コマンドの<text>には、空白がふくまれることがあるので注意してください。
各コマンドごとの処理を書くまえに、ひとつ準備をします。 405番コマンドのときは、データに<channel>しか含みませんでしたが、 これらのコマンドは複数の項目をふくんでいます。 そのため、データを複数の項目にきりわけて、個別に利用できるようにしておきたいです。 そこで、つぎのようにします。
ProcessCommand手続きで、varのところにTStrings型の変数datasetを追加します。
procedure TForm1.ProcessCommand(Socket: TCustomWinSocket; id: Integer; data: String); var room: TChannelFrame; sh: TTabSheet; dataset: TStrings;
そして、ProcessCommand手続きの中身をつぎのようにします。
begin dataset:=TStringList.Create;//dataset変数を作成する dataset.Delimiter:=' '; //コマンドデータの区切り文字は半角空白 dataset.DelimitedText:=data;//コマンドデータを区切ってdatasetに入れる try case id of (略) end; finally dataset.Free;//dataset変数を破棄する end; end;
こうすると、受け取ったデータの各項目を、 dataset[0],dataset[1],...のように指定して呼び出すことができます。
それでは、各コマンドの処理を書きます。
ProcessCommandのcase id of...の中に、つぎのように書き足すことになります。
メッセージを表示するチャット窓をさがすための関数を、とりあえず
FindChannelWindowとしておきます。チャンネル名を引数にとります。
403: //チャンネルメッセージ //<channel> <nick> <text> (<text>は空白を含む) begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; room.Memo1.Lines.Add('<'+dataset[1]+'> ' +Copy(data,Length(dataset[0]+' '+dataset[1]+' ')+1, Length(data)-Length(dataset[0]+' '+dataset[1]+' '))); end;
410: //チャンネルトピック //<channel> <topic> begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; room.Memo1.Lines.Add(dataset[1]); end;
824: //emote //<channel> <user> "<text>" begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; room.Memo1.Lines.Add('* '+dataset[1]+' '+dataset[2]); end;
FindChannelWindowはつぎのように宣言・実装します。
宣言
function FindChannelWindow(channel: String): TChannelFrame;
実装
function TForm1.FindChannelWindow(channel: String): TChannelFrame; var i: Integer; begin Result:=nil; with PageControl1 do for i:=0 to PageCount-1 do if Pages[i].Caption=channel then begin Result:=Pages[i].Components[0] as TChannelFrame; exit; end; end;
実行してみます。 これで、トピックやチャンネルメッセージをメッセージ表示欄に表示できるようになりました。 つぎに、メンバー表示欄でメンバーを表示・追加・削除できるようにしましょう。
入室メンバーの表示まわりのコードをかきます。
409番のための処理はとくにありません。 他のメンバーの入室時にはメンバーを表示欄に追加、 メンバーの退室時には削除したいです。 ProcessCommandのcase id of...の中に、つぎのように書き足すことになります。
406: //入室メッセージ //<channel> <user> <sharing> <link-type> begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; with room.ListView1.Items.Add do begin Caption:=dataset[1]; SubItems.Add(dataset[2]); SubItems.Add(dataset[3]); end; room.Memo1.Lines.Add('+ '+dataset[1]+' ('+dataset[3]+') [sharing ' +dataset[2]+' files] has joined.'); end; 407: //ユーザーがチャンネルから退室 //<channel> <nick> <sharing> <linespeed> begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; room.ListView1.FindCaption(0,dataset[1],false,false,true).Delete; room.Memo1.Lines.Add('- '+dataset[1]+' ('+dataset[3]+') [sharing ' +dataset[2]+' files] has left.'); end; 408: //チャンネルユーザーの一覧の項目 //<channel> <user> <sharing> <link-type> begin room:=FindChannelWindow(dataset[0]); if room=nil then exit; with room.ListView1.Items.Add do begin Caption:=dataset[1]; SubItems.Add(dataset[2]); SubItems.Add(dataset[3]); end; end;
実行してみます。 これで、メンバー表示欄にメンバーの入室・退室を反映できるようになりました。 つぎに、チャット窓のメッセージ入力欄からメッセージを送れるようにしましょう。
チャット窓のメッセージ入力欄を実装します。 任意のコマンドを送れるクライアントのコマンド入力欄と、だいたいおなじですが、 入力した文字列の先頭が'/'じゃないときの処理を追加します。
メニュー→表示→フォームで「フォームの表示」ダイアログを出し、 ChannelFrameを選択し、OKボタンをおします。
つぎに、オブジェクトツリーでChannelFrameのMemo2を選択し、 オブジェクトインスペクタでMemo2のOnKeyDownイベントの欄をダブルクリックして、 TChannelFrame.Memo2KeyDownを自動的に作成します。
そして、つぎのように中身をかきます。 TForm1.Memo2KeyDownと似ていますが、 str[1]が'/'でないときはチャンネルメッセージとして送るようになっています。
procedure TChannelFrame.Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var str,data: String; id: Integer; begin if Key<>VK_Return then exit; str:=TrimRight(Memo2.Text)+' '; if Length(str)<2 then exit; if str[1]<>'/' then begin Form1.SendNapCommand(Form1.ClientSocket1.Socket,403, (Self.Parent as TTabSheet).Caption+' '+str); end else begin id:=StrToIntDef(Copy(str,2,Pos(' ',str)-2),-1); data:=Copy(str,Pos(' ',str)+1,Length(str)-Pos(' ',str)-1); Form1.SendNapCommand(Form1.ClientSocket1.Socket,id,data); end; Memo2.Clear; end;
このコードでは、Form1を参照しようとしているため、実行する前に次のように implementation部のusesにUnit1を追加しておいてください。
implementation {$R *.dfm} uses Unit1;
実行してみます。メッセージ入力欄になにかことばをいれて、 Enterキーをおしてみてください。
チャット窓のメッセージ入力欄からメッセージをおくれました。 さいごに、退室のための処理をコードにします。
退室のながれとしては、
というかんじです。
チャット窓をとじる部分は、すこしむずかしいかもしれません。 Button1Clickで直接
PageControl1.ActivePage.Free;
とか
(TChannelFrame(Self.Parent).Parent as TTabSheet).Free;
としても、 実行時に例外がでてうまくいきません。なぜかというと、 処理のながれは、チャット窓が閉じられるのを待ってから Button1Clickにもどってこようとするのですが、 チャット窓を閉じたときにButton1もなくなってしまっているので、 Button1Clickにもどってこれないからです。
これを解決するには、PostMessage関数をつかいます。 Button1ClickでPostMessageを使って、 PageControl1にウィンドウメッセージをとばし、 PageControl1にチャット窓をとじてもらいます。 PostMessageはチャット窓がとじられるまでまたずに 処理の流れをButton1Clickにもどすので、 例外をださずにチャット窓をとじれます。
具体的には、まず、Unit2.pasでウィンドウメッセージのIDを予約します。
Unit2.pas
const WM_ACTIVEPAGE_FREE=WM_USER+1;
つぎに、オブジェクトインスペクタでフレームChannelFrameのButton1のOnClickイベントの欄をダブルクリックし、対応する手続きButton1Clickを自動的に宣言・実装します。
そして、Button1Clickの中身をつぎのように書きます。 PostMessageでForm1にウィンドウメッセージWM_ACTIVEPAGE_FREEを送っています。
procedure TChannelFrame.Button1Click(Sender: TObject); begin Form1.SendNapCommand(Form1.ClientSocket1.Socket,401, (Self.Parent as TTabSheet).Caption); PostMessage(Form1.Handle,WM_ACTIVEPAGE_FREE, 0, 0); end;
ここまでできたら、つぎはForm1側がこのメッセージをうけとったときの動作を書きます。 Unit1.pasで、Form1がWM_ACTIVEPAGE_FREEメッセージをうけとったときの動作を WMActivePageFreeという手続きにして、つぎのように宣言・実装します。 宣言
procedure WMActivePageFree(var Message: TMessage); message WM_ACTIVEPAGE_FREE;
実装
procedure TForm1.WMActivePageFree; begin PageControl1.ActivePage.Free; //表示しているページをとじる end;
実行してみます。 ひらいたチャット窓のButton1をおしてみてください。
退室コマンドを送り、チャット窓をとじることができました。
ここまでのコードをのせておきます。
ここでは、チャットできるクライアントのしあげとして、つぎの2つのことをします。
長い名前のユーザーがチャットにいるとき、メンバー表示欄の幅をひろげたいことがあります。 メッセージ表示欄とメンバー表示欄の境界を左右にずらせると便利ですね。 こういうことができるのがスプリッタです。
さきにパネルコントロールをはりつけ、そこにMemo1とListView1を移動します。 パネルコントロールはコンポーネントパレットのStandardタブにあります。 パネルコントロールの名前はPanel1となっているとおもいます。
Panel1のAlignプロパティをalTopにし、縦の幅をてきとうに調節したあと
とします。
Memo1はAlignプロパティをalClientに、 ListView1はAlignプロパティをalRightにします。
つぎにスプリッタをはりつけます。 スプリッタコントロールは、コンポーネントパレットのAdditionalタブにあります。 はりつけたスプリッタは、Splitter1という名前になるとおもいます。 Panel1の上にうまくのっていなかったら、 オブジェクトツリーでSplitter1をPanel1にドラッグ&ドロップしてください。
Splitter1のAlignプロパティをalRight、Widthプロパティの値を3にします。
実行してみます。メッセージ表示欄とメンバー表示欄の境目を、 ドラッグ操作で左右に移動できるようになりました。
ChannelFrameにMemo2やButton1をはりつけたとき、 もしMemo2をはりつける順番がButton1よりあとだと、 チャット窓を開いたときは、Button1にフォーカスがきているはずです。 たいていの場合、チャット窓を開いてからすることはメッセージの入力ですから、
という動きを毎回やることになり、面倒です。 これを、チャット窓を開いたときに、はじめからMemo2にフォーカスがくるようにすれば、
と、操作がかなりラクになります。
具体的にすることは、Memo2のTabOrderプロパティの値を0にするだけです。 これで、チャット窓のタブ順はMemo2が一番はじめになります。 もしすでにTabOrderプロパティが0なら、入室からメッセージ入力までスムーズに 行えているはずです。
実行してみます。チャット窓をひらくと、メッセージ入力欄にフォーカスがきていて、 すぐにメッセージを入力できる状態になっています。
ついでに、クライアントの起動時から接続ボタンを押す動作、入室コマンドの送信までが 行いやすいようにしておきましょう。Form1について、以下のことをします。
これで、起動時にスペースキーで接続→Tabキーでコマンド入力欄にフォーカス→メッセージ入力という流れになって、とても操作しやすくなりました。
これで、チャットができるクライアントのできあがりです。
ここまでのコードをのせておきます。