• WCF 聊天室程序代码详细讲解教程


    解决方案

    ChatService 服务端主要的三个文件:App.config,ChatService.cs,Program.cs

    FormChatClient 客户端主要二个文件:App.config,ChatForm.cs

    以下为这五个文件的全部代码及讲解,因为打算放在一篇文章里,所以本文会很长。发由本教程目的并不仅仅让初学者了解怎么开发一个聊天室。而是要通过这个例子

    加深对C#及WCF一些实用特性的了解。

    1 Service App.config
    1. <xml version="1.0" encoding="utf-8" >
    2. <configuration>
    3.   <appSettings>
    4.     <!--提供服务的通信协议、地址、端口、目录-->
    5.     <!--通信协议:net.tcp 、http 、-->
    6.     <add key="addr" value="net.tcp://localhost:22222/chatservice" />
    7.   </appSettings>
    8.   <system.serviceModel>
    9.     <services>
    10.       <!--服务名 = <命名空间>.<程序集名称>-->
    11.       <!--behaviorConfiguration 性能配置自定一个名称,<serviceBehaviors> 下的项对应此名称-->
    12.       <service name="NikeSoftChat.ChatService" behaviorConfiguration="MyBehavior">
    13.         <!--终节点-->
    14.         <!--binding 绑定类型,NetTcpBinding、WSDualHttpBinding、WSHttpBindingBase、BasicHttpBinding、NetNamedPipeBinding、NetPeerTcpBinding、MsmqBindingBase、NetPeerTcpBinding、WebHttpBinding、MailBindingBase、CustomBinding-->
    15.         <!--DuplexBinding 双工-->
    16.         <!--使用契约:<命名空间>.<接口名称>-->
    17.         <endpoint address=""
    18.                   binding="netTcpBinding"
    19.                   bindingConfiguration="DuplexBinding"
    20.                   contract="NikeSoftChat.IChat" />
    21.       </service>
    22.     </services>
    23.     <behaviors>
    24.       <serviceBehaviors>
    25.         <behavior  name="MyBehavior">
    26.           <!--会话最大数量-->
    27.           <serviceThrottling maxConcurrentSessions="10000" />
    28.         </behavior>
    29.       </serviceBehaviors>
    30.     </behaviors>
    31.     <bindings>
    32.       <netTcpBinding>
    33.         <!--双工,超时设置-->
    34.         <binding name="DuplexBinding" sendTimeout="00:00:01">
    35.           <!--可靠会话-->
    36.           <reliableSession enabled="true" />
    37.           <!--安全模式-->
    38.           <security mode="None" />
    39.         </binding>
    40.       </netTcpBinding>
    41.     </bindings>
    42.   </system.serviceModel>
    43. </configuration>
    复制代码
    2 Service ChatService.cs
    1. // Copyright (C) 2006 by Nikola Paljetak
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.ServiceModel;
    6. namespace NikeSoftChat
    7. {
    8.     //服务契约
    9.     // SessionMode.Required  允许Session会话
    10.     // 双工协定时的回调协定类型为IChatCallback接口)
    11.     [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
    12.     interface IChat
    13.     {
    14.         //服务操作
    15.         // IsOneWay = false      等待服务器完成对方法处理
    16.         // IsInitiating = true  启动Session会话
    17.         // IsTerminating = false 设置服务器发送回复后不关闭会话
    18.         [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
    19.         string[] Join(string name);
    20.         [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    21.         void Say(string msg);
    22.         [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    23.         void Whisper(string to, string msg);
    24.         //服务操作
    25.         // IsOneWay = true      不等待服务器完成对方法处理
    26.         // IsInitiating = false  不启动Session会话
    27.         // IsTerminating = true  关闭会话,在服务器发送回复后
    28.         [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
    29.         void Leave();
    30.     }
    31.     interface IChatCallback
    32.     {
    33.         [OperationContract(IsOneWay = true)]
    34.         void Receive(string senderName, string message);
    35.         [OperationContract(IsOneWay = true)]
    36.         void ReceiveWhisper(string senderName, string message);
    37.         [OperationContract(IsOneWay = true)]
    38.         void UserEnter(string name);
    39.         [OperationContract(IsOneWay = true)]
    40.         void UserLeave(string name);
    41.     }
    42.     //定义一个客户端动作的枚举
    43.     public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper };
    44.     //定义一个本例的事件消息类
    45.     public class ChatEventArgs : EventArgs
    46.     {
    47.         public MessageType msgType;
    48.         public string name;
    49.         public string message;
    50.     }
    51.     // InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象
    52.     // ConcurrencyMode.Multiple      异步的多线程实例
    53.     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    54.     public class ChatService : IChat //继承IChat接口或者说IChat的实现类
    55.     {
    56.         //定义一个静态对象用于线程部份代码块的锁定,用于lock操作
    57.         private static Object syncObj = new Object();
    58.         //创建一个IChatCallback 回调接口实例,接口成员始终是公共的,所有没有访问修饰符
    59.         IChatCallback callback = null;
    60.         //定义一个委托
    61.         public delegate void ChatEventHandler(object sender, ChatEventArgs e);
    62.         //定义一个静态的委托事件
    63.         public static event ChatEventHandler ChatEvent;
    64.         //创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类
    65.         static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();
    66.         //当用客户的昵称
    67.         private string name;
    68.         //创建委托(ChatEventHandler)的一个空实例
    69.         private ChatEventHandler myEventHandler = null;
    70.         //成员进入聊天室
    71.         public string[] Join(string name)
    72.         {
    73.             bool userAdded = false;
    74.             //用MyEventHandler方法,实例化委托(ChatEventHandler)
    75.             myEventHandler = new ChatEventHandler(MyEventHandler);
    76.             //锁定,保持lock块中的代码段始终只有一个线程在调用,原因是ConcurrencyMode.Multiple 为异步的多线程实例,存在并发竞争问题
    77.             //如果不锁定,则静态成员字典chatters.ContainsKey(name) 的结果将会不确定,原因是每个线程都可以访问到它。以下凡是chatters 的操作匀加锁
    78.             //使用lock多个线程同时请示时,没有操作权的将会在线程池中等待至有操作权的线程执完成。lock 方法存在影响吞吐量的问题
    79.             lock (syncObj)
    80.             {
    81.                 //如果请求的昵称在成员字典中不存在并不空
    82.                 if (!chatters.ContainsKey(name) && name != "" && name != null)
    83.                 {
    84.                     this.name = name; //记录当前线程昵称
    85.                     chatters.Add(name, MyEventHandler);//加入到成员字典key 为当前昵称,MyEventHandler 当前的委托调用
    86.                     userAdded = true;
    87.                 }
    88.             }
    89.             if (userAdded)
    90.             {
    91.                 //获取当前操作客户端实例的通道给IChatCallback接口的实例callback,
    92.                 //此通道是一个定义为IChatCallback类型的泛类型
    93.                 //通道的类型是事先服务契约协定好的双工机制(见IChat前的ServiceContract)
    94.                 callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
    95.                 //实例化事件消息类ChatEventArgs,并对其赋值
    96.                 ChatEventArgs e = new ChatEventArgs();
    97.                 e.msgType = MessageType.UserEnter;
    98.                 e.name = name;
    99.                 //发送广播信息
    100.                 BroadcastMessage(e);
    101.                 //加入到多路广播委托的调用列表中,下面这条如果和上面一条位置互换,那么会收到自己进入聊天室的广播信息。
    102.                 ChatEvent += myEventHandler;
    103.                 //以下代码返回当前进入聊天室成员的称列表
    104.                 string[] list = new string[chatters.Count];
    105.                 lock (syncObj)
    106.                 {
    107.                     chatters.Keys.CopyTo(list, 0);//从成员字典索引0 开始复制chatters成员字典的key 值到list 字符串数组
    108.                 }
    109.                 return list;
    110.             }
    111.             else
    112.             {
    113.                 //当昵称重复或为空是,如果客户端做了为空检测,则可直接认为是名称重复,当前要在没有异常的情况下。
    114.                 return null;
    115.             }
    116.         }
    117.         //聊天室通信
    118.         public void Say(string msg)
    119.         {
    120.             ChatEventArgs e = new ChatEventArgs();
    121.             e.msgType = MessageType.Receive;
    122.             e.name = this.name;
    123.             e.message = msg;
    124.             BroadcastMessage(e);
    125.         }
    126.         //私有对话
    127.         public void Whisper(string to, string msg)
    128.         {
    129.             ChatEventArgs e = new ChatEventArgs();
    130.             e.msgType = MessageType.ReceiveWhisper;
    131.             e.name = this.name;
    132.             e.message = msg;
    133.             try
    134.             {
    135.                 //创建一个临时委托实例
    136.                 ChatEventHandler chatterTo;
    137.                 lock (syncObj)
    138.                 {
    139.                     //查找成员字典中,找到要接收者的委托调用
    140.                     chatterTo = chatters[to];
    141.                 }
    142.                 //异步方式调用接收者的委托调用
    143.                 chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
    144.             }
    145.             catch (KeyNotFoundException)
    146.             {
    147.                 //访问集合中元素的键与集合中的任何键都不匹配时所引发的异常
    148.             }
    149.         }
    150.         //成员离开聊天室
    151.         public void Leave()
    152.         {
    153.             if (this.name == null)
    154.                 return;
    155.             //删除成员字典中的当前会话的成员,及删除多路广播委托的调用列表中的当前调用
    156.             //name 和myEventHandler 的生存周期是在当前会话中一直存在的,参考Session 周期
    157.             lock (syncObj)
    158.             {
    159.                 chatters.Remove(this.name);
    160.             }
    161.             ChatEvent -= myEventHandler;
    162.             ChatEventArgs e = new ChatEventArgs();
    163.             e.msgType = MessageType.UserLeave;
    164.             e.name = this.name;
    165.             this.name = null;
    166.             BroadcastMessage(e);
    167.         }
    168.         //回调
    169.         //根据客户端动作通知对应客户端执行对应的操作
    170.         private void MyEventHandler(object sender, ChatEventArgs e)
    171.         {
    172.             try
    173.             {
    174.                 switch (e.msgType)
    175.                 {
    176.                     case MessageType.Receive:
    177.                         callback.Receive(e.name, e.message);
    178.                         break;
    179.                     case MessageType.ReceiveWhisper:
    180.                         callback.ReceiveWhisper(e.name, e.message);
    181.                         break;
    182.                     case MessageType.UserEnter:
    183.                         callback.UserEnter(e.name);
    184.                         break;
    185.                     case MessageType.UserLeave:
    186.                         callback.UserLeave(e.name);
    187.                         break;
    188.                 }
    189.             }
    190.             catch //异常退出,或超时,或session过期
    191.             {
    192.                 Leave();
    193.             }
    194.         }
    195.         //发送广播信息
    196.         //要点:根据上下文理解: 1 广播什么(what),2 为谁广播(who),3“谁”从哪来(where),4 如何来的(how)
    197.         private void BroadcastMessage(ChatEventArgs e)
    198.         {
    199.             //创建回调委托事件实例ChatEvent的一个副本,之所以用副本是因为ChatEvent处于多线程并状态(?此处不知理解是否正确,因为我理解后面的handler 是一个引用,自相矛盾了)
    200.             ChatEventHandler temp = ChatEvent;
    201.             if (temp != null)
    202.             {
    203.                 //GetInvocationList方法,按照调用顺序返回“多路广播委托(MulticastDelegate)”的调用列表
    204.                 foreach (ChatEventHandler handler in temp.GetInvocationList())
    205.                 {
    206.                     //异步方式调用多路广播委托的调用列表中的ChatEventHandler
    207.                     //BeginInvoke方法异步调用,即不等等执行,详细说明则是:公共语言运行库(CLR) 将对请求进行排队并立即返回到调用方。将对来自线程池的线程调用该目标方法。
    208.                     //EndAsync 为线程异步调用完成的回调方法,EndAsync 接收并操持着线程异步调用的操作状态,可通过此结果找到调用者,如此例handler,handler是一个委托实例的引用
    209.                     //        此状态为调用者(委托)的事件声明类型此例为public event ChatEventHandler ChatEvent; 中的ChatEventHandler
    210.                     //最后一个参数:包含的对象的状态信息,传递给委托;
    211.                     handler.BeginInvoke(this, e,EndAsync, null);
    212.                 }
    213.             }
    214.         }
    215.         //广播中线程调用完成的回调方法
    216.         //功能:清除异常多路广播委托的调用列表中异常对象(空对象)
    217.         private void EndAsync(IAsyncResult ar)
    218.         {
    219.             ChatEventHandler d = null;
    220.             try
    221.             {
    222.                 //封装异步委托上的异步操作结果
    223.                 System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar;
    224.                 //asres.AsyncDelegate 获取在异步调用asres 的委托对象,asres 来自对ar 的AsyncResult 封装,ar 来自线程异步调用的操作状态
    225.                 d = ((ChatEventHandler)asres.AsyncDelegate);
    226.                 //EndInvoke 返回由异步操作ar 生成的结果Object
    227.                 d.EndInvoke(ar);
    228.             }
    229.             catch
    230.             {
    231.                 ChatEvent -= d;
    232.             }
    233.         }
    234.     }
    235. }
    3 Service Program.cs
    1. // Copyright (C) 2006 by Nikola Paljetak
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.ServiceModel;
    6. using System.Configuration;
    7. namespace NikeSoftChat
    8. {
    9.     class Program
    10.     {
    11.         static void Main(string[] args)
    12.         {
    13.             Uri uri = new Uri(ConfigurationManager.AppSettings["addr"]);//获取配置
    14.             ServiceHost host = new ServiceHost(typeof(NikeSoftChat.ChatService), uri);
    15.             host.Open();
    16.             Console.WriteLine("Chat service listen on endpoint {0}", uri.ToString());
    17.             Console.WriteLine("Press ENTER to stop chat service...");
    18.             Console.ReadLine();
    19.             host.Abort();
    20.             host.Close();
    21.         }
    22.      
    23.     }
    24. }
    复制代码
    4 Client  App.config
    1. <xml version="1.0" encoding="utf-8">
    2. <!--这里的说明可以完全参考 service 的 app.config -->
    3. <configuration>
    4.   <system.serviceModel>
    5.     <client>
    6.       <endpoint name=""
    7.                 address="net.tcp://localhost:22222/chatservice"
    8.                 binding="netTcpBinding"
    9.                 bindingConfiguration="DuplexBinding"
    10.                 contract="IChat" />
    11.     </client>
    12.     <bindings>
    13.       <netTcpBinding>
    14.         <!--这里的sendTimeout比服务端的要多出4秒,因为服务端不参入具体通信,它只是提供服务-->
    15.         <binding name="DuplexBinding" sendTimeout="00:00:05" >
    16.           <reliableSession enabled="true" />
    17.           <security mode="None" />
    18.         </binding>
    19.       </netTcpBinding>
    20.     </bindings>
    21.   </system.serviceModel>
    22. </configuration>
    复制代码
    5 Client ChatForm.cs
    1. // Copyright (C) 2006 by Nikola Paljetak
    2. using System;
    3. using System.Collections.Generic;
    4. using System.ComponentModel;
    5. using System.Data;
    6. using System.Drawing;
    7. using System.Text;
    8. using System.Windows.Forms;
    9. using System.Runtime.InteropServices;
    10. using System.ServiceModel;
    11. namespace NikeSoftChat
    12. {
    13.     public partial class ChatForm : Form, IChatCallback
    14.     {
    15.         [DllImport("user32.dll")]
    16.         private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
    17.         private const int WM_VSCROLL = 0x115;
    18.         private const int SB_BOTTOM = 7;
    19.         private int lastSelectedIndex = -1;
    20.         private ChatProxy proxy;//代理
    21.         private string myNick;  //当前我的昵称
    22.         private PleaseWaitDialog pwDlg; //状态窗口(显示等待与错误提示)
    23.         private delegate void HandleDelegate(string[] list);//委托
    24.         private delegate void HandleErrorDelegate();//委托
    25.         public ChatForm()
    26.         {
    27.             InitializeComponent();
    28.             ShowConnectMenuItem(true);
    29.         }
    30.         private void connectToolStripMenuItem_Click(object sender, EventArgs e)
    31.         {
    32.             lstChatters.Items.Clear();
    33.             NickDialog nickDlg = new NickDialog();
    34.             if (nickDlg.ShowDialog() == DialogResult.OK)
    35.             {
    36.                 myNick = nickDlg.txtNick.Text;// 得到键入的当前昵称
    37.                 nickDlg.Close();
    38.             }
    39.             txtMessage.Focus();
    40.             Application.DoEvents(); //强制处理当前队列的所有windows 消息,以名影响后面的通信,winForm程序要注意一下UI 线程的问题
    41.             //获取服务实例的上下文,为指定主机承载的服务初始化
    42.             InstanceContext site = new InstanceContext(this);
    43.             proxy = new ChatProxy(site); //初始服务实例
    44.             //BeginJoin 是svcutil工具自动生成的(如果你用工具的话,当然你也可以自己写),还有一个EndJoin 也是。
    45.             //为什么会生成这两个我们并没有在服务契约中定义的接口呢?原因是服务契约中Join 接口定义了IsOneWay = false
    46.             //IsOneWay = false 则我们在配置中绑定的是duplex 双工(双通道),指操作返回应答信息。
    47.             //duplex 双工并不会等待调用服务方法完成,而是立即返回。单工方式则为Request/Reply 本例中没有涉及
    48.             //自动生成BeginJoin 会比我们的Join 多两个参数,一个用来当BeginJoin请求在服务方完成后本地回调的方法,另一个获取作为BeginInvoke 方法调用的最后一个参数而提供的对象。
    49.             //BeginJoin 会请求服务方执行Join 方法。当Join代码执行完毕,触发回调方法OnEndJoin
    50.             IAsyncResult iar = proxy.BeginJoin(myNick, new AsyncCallback(OnEndJoin),null);
    51.             pwDlg = new PleaseWaitDialog();
    52.             pwDlg.ShowDialog();
    53.         }
    54.         private void OnEndJoin(IAsyncResult iar)
    55.         {
    56.             try
    57.             {
    58.                 //EndJoin 请求服务方返回Join 执行后的返回值
    59.                 //iar 异步调用的操作状态
    60.                 //返回聊天室当前在线成员列表
    61.                 string[] list = proxy.EndJoin(iar);
    62.                 HandleEndJoin(list);
    63.             }
    64.             catch (Exception e)
    65.             {
    66.                 HandleEndJoinError();
    67.             }
    68.         }
    69.         private void HandleEndJoinError()
    70.         {
    71.             //判断状态提示窗口是否在同一个线程内
    72.             if (pwDlg.InvokeRequired)
    73.                 //对当前线程调用目标方法,此例调用本身
    74.                 pwDlg.Invoke(new HandleErrorDelegate(HandleEndJoinError));
    75.             else
    76.             {
    77.                 pwDlg.ShowError("Error: Cannot connect to chat!");
    78.                 ExitChatSession();
    79.             }
    80.         }
    81.         //生成在线成员列表,当参数list为空时则表示当前昵称在在线成员列表中已存在
    82.         private void HandleEndJoin(string[] list)
    83.         {
    84.             //状态提示窗口是否运行在同一个线程
    85.             if (pwDlg.InvokeRequired)
    86.                 pwDlg.Invoke(new HandleDelegate(HandleEndJoin), new object[] { list });
    87.             else
    88.             {
    89.                 if (list == null)
    90.                 {
    91.                     pwDlg.ShowError("Error: Username already exist!");
    92.                     ExitChatSession();
    93.                 }
    94.                 else
    95.                 {
    96.                     pwDlg.Close();
    97.                     ShowConnectMenuItem(false);
    98.                     foreach (string name in list)
    99.                     {
    100.                         lstChatters.Items.Add(name);
    101.                     }
    102.                     AppendText("Connected at " + DateTime.Now.ToString() + " with user name " + myNick + Environment.NewLine);
    103.                 }
    104.             }
    105.         }
    106.         //发送信息
    107.         private void SayAndClear(string to, string msg, bool pvt)
    108.         {
    109.             if (msg != "")
    110.             {
    111.                 try
    112.                 {
    113.                     CommunicationState cs = proxy.State;
    114.                     //pvt 公聊还是私聊
    115.                     if (!pvt)
    116.                         proxy.Say(msg); //发送信息
    117.                     else
    118.                         proxy.Whisper(to, msg);//对指定者发送信息
    119.                     txtMessage.Text = "";
    120.                 }
    121.                 catch
    122.                 {
    123.                     AbortProxyAndUpdateUI();
    124.                     AppendText("Disconnected at " + DateTime.Now.ToString() + Environment.NewLine);
    125.                     Error("Error: Connection to chat server lost!");
    126.                 }
    127.             }
    128.         }
    129.         private void Error(string errMessage)
    130.         {
    131.             MessageBox.Show(errMessage, "Connection error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    132.             ExitChatSession();
    133.         }
    134.         private void btnSay_Click(object sender, EventArgs e)
    135.         {
    136.             SayAndClear("", txtMessage.Text, false);
    137.             txtMessage.Focus();
    138.         }
    139.         private void btnWhisper_Click(object sender, EventArgs e)
    140.         {
    141.             if (txtMessage.Text == "")
    142.                 return;
    143.             object to = lstChatters.SelectedItem;
    144.             if (to != null)
    145.             {
    146.                 string receiverName = (string)to;
    147.                 AppendText("Whisper to " + receiverName + ": " + txtMessage.Text + Environment.NewLine);
    148.                 SayAndClear(receiverName, txtMessage.Text, true);
    149.                 txtMessage.Focus();
    150.             }
    151.         }
    152.         private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
    153.         {
    154.             ExitChatSession();
    155.             btnWhisper.Enabled = false;
    156.             AppendText("Disconnected at " + DateTime.Now.ToString() + Environment.NewLine);
    157.         }
    158.         #region IChatCallback 实现接口成员
    159.         //接收公聊时
    160.         public void Receive(string senderName, string message)
    161.         {
    162.             AppendText(senderName + ": " + message + Environment.NewLine);
    163.         }
    164.         //接收私聊时
    165.         public void ReceiveWhisper(string senderName, string message)
    166.         {
    167.             AppendText(senderName + " whisper: " + message + Environment.NewLine);
    168.         }
    169.         //其他人进入聊天室时
    170.         public void UserEnter(string name)
    171.         {
    172.             AppendText("User " + name + " enter at " + DateTime.Now.ToString() + Environment.NewLine);
    173.             lstChatters.Items.Add(name);
    174.         }
    175.         //其他人退出聊天室时
    176.         public void UserLeave(string name)
    177.         {
    178.             AppendText("User " + name + " leave at " + DateTime.Now.ToString() + Environment.NewLine);
    179.             lstChatters.Items.Remove(name);
    180.             AdjustWhisperButton();
    181.         }
    182.         #endregion
    183.         private void AppendText(string text)
    184.         {
    185.             txtChatText.Text += text;
    186.             //txtChatText 滚动条始终定位于最下面
    187.             SendMessage(txtChatText.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
    188.         }
    189.         //菜单项"连接"与"断开"的显/隐控制()
    190.         private void ShowConnectMenuItem(bool show)
    191.         {
    192.             connectToolStripMenuItem.Enabled = show;
    193.             disconnectToolStripMenuItem.Enabled = btnSay.Enabled = !show;
    194.         }
    195.         private void txtMessage_KeyDown(object sender, KeyEventArgs e)
    196.         {
    197.             if (e.KeyData == Keys.Enter && btnSay.Enabled)
    198.             {
    199.                 SayAndClear("", txtMessage.Text, false);
    200.             }
    201.         }
    202.         private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    203.         {
    204.             ExitChatSession();
    205.             ExitApplication();
    206.         }
    207.         private void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
    208.         {
    209.             ExitChatSession();
    210.             ExitApplication();
    211.         }
    212.         //退出聊天室会话
    213.         private void ExitChatSession()
    214.         {
    215.             try
    216.             {
    217.                 //离开通知
    218.                 proxy.Leave();
    219.             }
    220.             catch { }
    221.             finally
    222.             {             
    223.                 AbortProxyAndUpdateUI();
    224.             }
    225.         }
    226.         //中断代理并更新UI
    227.         private void AbortProxyAndUpdateUI()
    228.         {
    229.             if (proxy != null)
    230.             {
    231.                 proxy.Abort();
    232.                 proxy.Close();
    233.                 proxy = null;
    234.             }
    235.             ShowConnectMenuItem(true);
    236.         }
    237.         private void ExitApplication()
    238.         {
    239.             Application.Exit();
    240.         }
    241.         private void txtMessage_KeyPress(object sender, KeyPressEventArgs e)
    242.         {
    243.             if (e.KeyChar == 13)
    244.             {
    245.                 e.Handled = true;
    246.                 btnSay.PerformClick();
    247.             }
    248.         }
    249.         private void lstChatters_SelectedIndexChanged(object sender, EventArgs e)
    250.         {
    251.             AdjustWhisperButton();
    252.         }
    253.         private void AdjustWhisperButton()
    254.         {
    255.             if (lstChatters.SelectedIndex == lastSelectedIndex)
    256.             {
    257.                 lstChatters.SelectedIndex = -1;
    258.                 lastSelectedIndex = -1;
    259.                 btnWhisper.Enabled = false;
    260.             }
    261.             else
    262.             {
    263.                 btnWhisper.Enabled = true;
    264.                 lastSelectedIndex = lstChatters.SelectedIndex;
    265.             }
    266.             txtMessage.Focus();
    267.         }
    268.         private void ChatForm_Resize(object sender, EventArgs e)
    269.         {
    270.             //txtChatText 滚动条始终定位于最下面
    271.             SendMessage(txtChatText.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
    272.         }
    273.     }
    274. }
  • 相关阅读:
    spring boot下载本地静态文件最实用
    非常实用的MySQL中if、ifnull函数以及case/when的使用
    java获取访问地址IP的简单方法
    Oracle数据库视图的创建以及使用
    http-post调用接口简单代码
    orale数据库to_char时间中英文转换
    java线程的简单实用
    java小数保留位数四舍五入
    二项式反演
    学习总结-后缀数组
  • 原文地址:https://www.cnblogs.com/lzjsky/p/2047409.html
Copyright © 2020-2023  润新知