Silverlight4配合WCF net.tcp实现在线聊天应用攻略2
2010-07-01 01:33 NicolasZhang 阅读(1863) 评论(14) 编辑 收藏 举报接上篇,完成了开发文档中的例子后我学习了一下小笨蛋写的例子,但是发现他并没有把应用写完,我就狗尾续貂吧再来说道说道。
上代码,在VS2010中创建一个Silverlight4的项目并使用ASP.NET网站承载。先实现服务端功能,由于要使用WCF net.tcp Activation服务所以在Web项目引用中添加对System.ServiceModel.Activation的引用。
首先在Web项目新建一个接口,定义WCF服务接口和回调接口用以规约服务端与客户端通信的方式:
IDuplexService.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.Threading; namespace MyWebChat.Web { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IDuplexService" in both code and config file together. /// <summary> /// WCF服务接口。定义Session模式为必须使用Session,定义回调契约类型为IChatCallBack /// </summary> [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))] public interface IChatService { //定义操作契约 //IsOneWay = false,等待服务器完成对方法处理 //IsInitiating = true,启动Session会话, //IsTerminating = false,设置服务器发送回复后不关闭会话 /// <summary> /// 用户加入 /// </summary> /// <param name="name">用户昵称</param> /// <returns>返回当前在线用户数组</returns> [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)] string[] Join(string name); /// <summary> /// 用户群发消息 /// </summary> /// <param name="senderName">消息发送者昵称</param> /// <param name="msg">消息内容</param> [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)] void Say(string senderName, string msg); /// <summary> /// 用户发送私聊消息 /// </summary> /// <param name="to">私聊接受者</param> /// <param name="msg">消息内容</param> [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)] void Whisper(string to, string msg); //设置IsTerminating =true,用户离开后关闭Session /// <summary> /// 用户离开聊天室 /// </summary> [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)] void Leave(); } /// <summary> /// 双向通信的回调接口 /// </summary> public interface IChatCallback { /// <summary> /// 客户端接收群发消息 /// </summary> /// <param name="senderName"></param> /// <param name="message"></param> [OperationContract(IsOneWay = true)] void Receive(string senderName, string message); /// <summary> /// 客户端接收私聊消息 /// </summary> /// <param name="senderName">发送者昵称</param> /// <param name="message">消息内容</param> [OperationContract(IsOneWay = true)] void ReceiveWhisper(string senderName, string message); /// <summary> /// 用户进入 /// </summary> /// <param name="name">进入的用户昵称</param> [OperationContract(IsOneWay = true)] void UserEnter(string name); /// <summary> /// 用户离开 /// </summary> /// <param name="name">离开的用户昵称</param> [OperationContract(IsOneWay = true)] void UserLeave(string name); /// <summary> /// 更新在线用户列表 /// </summary> /// <param name="list">在线用户列表</param> [OperationContract(IsOneWay = true)] void UpdateOnlineUserList(string[] list); } /// <summary> /// 设定消息的类型 /// </summary> public enum MessageType { UserEnter, UserLeave, Receive, ReceiveWhisper, UpdateOnlineUserList }; /// <summary> /// 定义一个本例的事件消息类. 创建包含有关事件的其他有用的信息的变量,只要派生自EventArgs即可。 /// </summary> public class ChatEventArgs : EventArgs { public MessageType msgType; public string name; public string message; } }
接着是最重要的部分,创建一个Silverlight-enabled WCF Service用以实现刚才定义的服务端接口。小笨蛋的注释写的比较详细我又加了一些应该比较清楚了: IDuplexService.svc.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.Threading; using System.ServiceModel.Activation; namespace MyWebChat.Web { //// InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象。ConcurrencyMode.Multiple 异步的多线程实例 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class ChatService : IChatService { private static Object syncObj = new Object();////定义一个静态对象用于线程部份代码块的锁定,用于lock操作 IChatCallback callback = null; //双向通信的回调接口 public delegate void ChatEventHandler(object sender, ChatEventArgs e);//定义用于把处理程序赋予给事件的委托。 public static event ChatEventHandler ChatEvent;//定义事件 static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();//创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类 private string name; private ChatEventHandler myEventHandler = null; /// <summary> /// 用户加入 /// </summary> /// <param name="name">登录用户的昵称</param> /// <returns>当前用户昵称列表</returns> public string[] Join(string name) { bool userAdded = false; myEventHandler = new ChatEventHandler(MyEventHandler);//将MyEventHandler方法作为参数传递给委托 lock (syncObj)//线程的同步性,同步访问多个线程的任何变量,利用lock(独占锁),确保数据访问的唯一性。 { //判断在线用户列表中是否已经存在此用户昵称,昵称为空和null的情况可以从客户端判断 if (name != null && name != "" && !chatters.ContainsKey(name)) { this.name = name; chatters.Add(name, MyEventHandler);//把当前用户加入到在线用户列表中 userAdded = true; } } if (userAdded) { /* * 获取当前操作客户端实例的通道给IChatCallback接口的实例callback,此通道是一个定义为IChatCallback类型的泛类型, * 通道的类型是事先服务契约协定好的双工机制。 */ callback = OperationContext.Current.GetCallbackChannel<IChatCallback>(); //实例化事件消息类ChatEventArgs ChatEventArgs e = new ChatEventArgs { msgType = MessageType.UserEnter, name = name }; BroadcastMessage(e); UpdateOnlineUserList();//更新其他用户的在线用户列表 ChatEvent += myEventHandler; string[] list = new string[chatters.Count]; //以下代码返回当前进入聊天室成员的称列表 lock (syncObj) { chatters.Keys.CopyTo(list, 0);//将字典中记录的用户信息复制到数组中返回。 } return list; } else { return null; } } /// <summary> /// 广播消息 /// </summary> /// <param name="e"></param> private void BroadcastMessage(ChatEventArgs e) { switch (e.msgType) { //广播用户进入消息 case MessageType.UserEnter: Say("系统消息", "欢迎[" + e.name + "]加入聊天室!"); break; //广播用户离开消息 case MessageType.UserLeave: Say("系统消息", "[" + e.name + "]已经离开聊天室!"); break; default: throw new Exception(); } } /// <summary> /// 更新客户端列表 /// </summary> /// <returns></returns> public string[] GetOnlineUserList() { string[] list = new string[chatters.Count]; //以下代码返回当前进入聊天室成员的称列表 lock (syncObj) { chatters.Keys.CopyTo(list, 0);//将字典中记录的用户信息复制到数组中返回。 } return list; } /// <summary> /// 对所有人说 /// </summary> /// <param name="msg"></param> public void Say(string senderName, string msg) { ChatEventArgs e = new ChatEventArgs { msgType = MessageType.Receive, name = senderName, message = msg }; try { ChatEventHandler chatterTo;//创建一个临时委托实例 lock (syncObj) { foreach (var chatter in chatters) { //不再发送给发送者本人 if (chatter.Key != senderName) { chatterTo = chatter.Value; //查找成员字典中,找到要接收者的委托调用 chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用 } } } } catch (KeyNotFoundException) { } } /// <summary> /// 私聊信息 /// </summary> /// <param name="to"></param> /// <param name="msg"></param> public void Whisper(string to, string msg) { ChatEventArgs e = new ChatEventArgs { msgType = MessageType.ReceiveWhisper, name = this.name, message = msg }; try { ChatEventHandler chatterTo;//创建一个临时委托实例 lock (syncObj) { chatterTo = chatters[to]; //查找成员字典中,找到要接收者的委托调用 } chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用 } catch (KeyNotFoundException) { } } /// <summary> /// 用户加入 /// </summary> public void Leave() { if (this.name == null) return; lock (syncObj) { chatters.Remove(this.name); } ChatEvent -= myEventHandler; ChatEventArgs e = new ChatEventArgs { msgType = MessageType.UserLeave, name = this.name }; this.name = null; BroadcastMessage(e); UpdateOnlineUserList();//更新其他用户的在线用户列表 } /// <summary> /// 更新在线用户列表 /// </summary> private void UpdateOnlineUserList() { ChatEventArgs e = new ChatEventArgs { msgType = MessageType.UpdateOnlineUserList }; try { ChatEventHandler chatterTo;//创建一个临时委托实例 lock (syncObj) { foreach (var chatter in chatters) { chatterTo = chatter.Value; //查找成员字典中,找到要接收者的委托调用 chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用 } } } catch (KeyNotFoundException) { } } //回调,根据客户端动作通知对应客户端执行对应的操作 private void MyEventHandler(object sender, ChatEventArgs e) { try { switch (e.msgType) { case MessageType.Receive: callback.Receive(e.name, e.message); break; case MessageType.ReceiveWhisper: callback.ReceiveWhisper(e.name, e.message); break; case MessageType.UserEnter: callback.UserEnter(e.name); break; case MessageType.UserLeave: callback.UserLeave(e.name); break; case MessageType.UpdateOnlineUserList: callback.UpdateOnlineUserList(GetOnlineUserList()); break; } } catch { Leave(); } } //广播中线程调用完成的回调方法功能:清除异常多路广播委托的调用列表中异常对象(空对象) private void EndAsync(IAsyncResult ar) { ChatEventHandler d = null; try { //封装异步委托上的异步操作结果 System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar; d = ((ChatEventHandler)asres.AsyncDelegate); d.EndInvoke(ar); } catch { ChatEvent -= d; } } } }
需要注意的是此处用到delegate的BeginInvoke和EndInvoke进行多线程异步处理委托,所以需注意在操作静态对象时需要加锁。 完成之后得对Web.config进行相应修改:
Web.config
<?xml version="1.0"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name=""> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> </behavior> </serviceBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="tcpBindingNoSecurity"> <security mode="None" /> </binding> </netTcpBinding> </bindings> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> <services> <service name="MyWebChat.Web.ChatService"> <endpoint address="net.tcp://localhost:4502/MyWebChat.Web/DuplexService.svc" binding="netTcpBinding" bindingConfiguration="tcpBindingNoSecurity" contract="MyWebChat.Web.IChatService" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> </system.serviceModel> </configuration>
生成一下解决方案服务端的工作就完成了,接下来进行客户端的创建。 在Silverlight项目中添加服务引用,点击Discover便可获得刚才创建的WCF服务,设置命名空间为ChatServiceReference并引入。 若此步能正确引入此服务则说明刚才创建、配置的WCF服务正确无误,否则会遇到各种错误,这时请参考上一篇Silverlight4配合WCF net.tcp实现在线聊天应用攻略1中提到设置要点进行检查、修正并重新生成服务项目,直到正确引入为止。
创建一个ChatServiceClientManager类以统一控制WCF服务代理的使用。 ChatServiceClientManager.cs
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using MyWebChat.ChatServiceReference; namespace MyWebChat.ServiceClient { public class ChatServiceClientManager { public static ChatServiceClient client = new ChatServiceClient(); //实例化一个WCF服务客户端 public static bool hasSignedIn = false;//记录是否已经登录 public static string currentUserName = "";//记录当前用户昵称 public static List<string> onlineUserList = new List<string>();//记录在线用户列表 /// <summary> /// 调用WCF服务给指定昵称的客户端发送消息 /// </summary> /// <param name="receiverName">接收者昵称</param> /// <param name="messageText">消息内容</param> /// <returns>消息发送后更新的显示</returns> public static string SendMessage(string receiverName, string messageText) { if (receiverName == "所有人") { //异步调用服务端定义的Say方法 client.SayAsync(ChatServiceClientManager.currentUserName, messageText); return "你对所有人说:" + messageText + Environment.NewLine; } else { //异步调用服务端定义的Whisper方法 client.WhisperAsync(receiverName, messageText); return "你对" + receiverName + "悄悄说:" + messageText + Environment.NewLine; } } } }
添加一个Silverlight Child Window用来做登录界面:
LoginWindow.xaml
<controls:ChildWindow x:Class="MyWebChat.LoginWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Width="400" Height="255" Title="登录" HasCloseButton="False" FontSize="12"> <Canvas x:Name="LayoutRoot" Background="Beige"> <TextBlock Text="Silverlight在线聊天室" Canvas.Left="64" Canvas.Top="53" FontSize="25"></TextBlock> <TextBlock Text="昵称:" Canvas.Left="77" Canvas.Top="135" FontSize="15" Height="30" VerticalAlignment="Center"></TextBlock> <TextBox x:Name="txtUserName" Height="30" Width="116" FontSize="16" Canvas.Left="129" Canvas.Top="132" KeyDown="txtUserName_KeyDown" ></TextBox> <Button x:Name="btnSignIn" Content="登录" FontSize="15" Height="30" Width="65" Canvas.Left="251" Canvas.Top="132" Click="btnSignIn_Click"></Button> </Canvas> </controls:ChildWindow>
LoginWindow.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using MyWebChat.ChatServiceReference; using MyWebChat.ServiceClient; namespace MyWebChat { public partial class LoginWindow : ChildWindow { public string actualUserName = "";//最终用户名 public LoginWindow() { InitializeComponent(); } /// <summary> /// 登录按钮方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSignIn_Click(object sender, RoutedEventArgs e) { //监听Join方法以获得返回的在线用户列表 ChatServiceClientManager.client.JoinCompleted += new EventHandler<JoinCompletedEventArgs>(client_JoinCompleted); actualUserName = txtUserName.Text == string.Empty ? "测试用户" + DateTime.Now.ToString() : txtUserName.Text; ChatServiceClientManager.client.JoinAsync(actualUserName);//异步调用Join方法加入聊天室 } /// <summary> /// 处理Join方法的返回值 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void client_JoinCompleted(object sender, JoinCompletedEventArgs e) { if (e.Result != null) { foreach (var userName in e.Result) { ChatServiceClientManager.onlineUserList.Add(userName); } Close();//成功登录后关闭此窗口 } else { MessageBox.Show("已存在重名用户,请重新输入昵称!", "提示", MessageBoxButton.OK); txtUserName.Text = string.Empty; } } /// <summary> /// 昵称输入框点击Enter即触发登录 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtUserName_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { btnSignIn_Click(null, null); } } } }
接下来开始制作聊天主窗体,界面做的比较烂,以后再改进吧。
MainPage.xaml
<UserControl x:Class="MyWebChat.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="600" d:DesignWidth="800"> <StackPanel x:Name="LayoutRoot" Background="White"> <StackPanel Orientation="Horizontal" Background="AliceBlue"> <ScrollViewer x:Name="scrollChatContentContainer" Height="500" Width="600"> <TextBlock x:Name="tbChatContent" Margin="0" Width="575" FontSize="15" TextWrapping="Wrap" SizeChanged="tbChatContent_SizeChanged"> </TextBlock> </ScrollViewer> <ListBox x:Name="lstOnlineUser" Height="500" Width="200" Background="LightGray" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectionChanged="lstOnlineUser_SelectionChanged"></ListBox> </StackPanel> <StackPanel Orientation="Horizontal"> <StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="tbMyName" FontSize="13"></TextBlock> <TextBlock Text="对" Width="19" FontSize="13"></TextBlock> <ComboBox x:Name="cbbSpeakTo" Width="200"></ComboBox> <TextBlock Text="说" Width="19" TextAlignment="Center" FontSize="13"></TextBlock> </StackPanel> <TextBox x:Name="txtChatContentInput" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" Height="80" Width="600" FontSize="14" KeyDown="txtChatContentInput_KeyDown"></TextBox> </StackPanel> <Button x:Name="btnSend" Content="发送" Height="100" Width="200" FontSize="20" Click="btnSend_Click"></Button> </StackPanel> <TextBlock x:Name="reply" Margin="0,0,0,0" FontSize="18"></TextBlock> </StackPanel> </UserControl>
MainPage.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ServiceModel; using System.ServiceModel.Channels; using MyWebChat.ChatServiceReference; using System.Windows.Browser; using MyWebChat.ServiceClient; namespace MyWebChat { public partial class MainPage : UserControl { LoginWindow loginWin = null;//登录子窗体 public MainPage() { InitializeComponent(); // HtmlPage.RegisterScriptableObject("MainPage", this); this.Loaded += new RoutedEventHandler(MainPage_Loaded); } /// <summary> /// 主界面加载完成事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void MainPage_Loaded(object sender, RoutedEventArgs e) { //为各个事件添加监听 ChatServiceClientManager.client.ReceiveReceived += new EventHandler<ReceiveReceivedEventArgs>(client_ReceiveReceived); ChatServiceClientManager.client.ReceiveWhisperReceived += new EventHandler<ReceiveWhisperReceivedEventArgs>(client_ReceiveWhisperReceived); ChatServiceClientManager.client.UpdateOnlineUserListReceived += new EventHandler<UpdateOnlineUserListReceivedEventArgs>(client_UpdateOnlineUserListReceived); //打开注册子窗体 loginWin = new LoginWindow(); loginWin.Closed += new EventHandler(login_Closed); loginWin.Show(); } //[ScriptableMember()] /// <summary> /// 用户退出方法,通知服务端用户退出聊天室 /// </summary> public void UserQuit() { ChatServiceClientManager.client.LeaveAsync(); } //收到私聊消息处理方法 void client_ReceiveWhisperReceived(object sender, ReceiveWhisperReceivedEventArgs e) { //此处不太明白为什么Error不为空时才是有效的呢??? if (e.Error == null) { tbChatContent.Text += "[" + e.senderName + "]悄悄对你说:" + e.message + Environment.NewLine; } } //收到群发消息处理方法 void client_ReceiveReceived(object sender, ReceiveReceivedEventArgs e) { //此处不太明白为什么Error不为空时才是有效的呢??? if (e.Error == null) { string msg = e.senderName == "系统消息" ? e.senderName + ":" + e.message : "[" + e.senderName + "]对所有人说:" + e.message; tbChatContent.Text += msg + Environment.NewLine; } } //收到更新在线用户列表处理方法 void client_UpdateOnlineUserListReceived(object sender, UpdateOnlineUserListReceivedEventArgs e) { e.list.Insert(0, "所有人"); lstOnlineUser.ItemsSource = e.list; List<string> userListForSpeakTo = new List<string>(); userListForSpeakTo.AddRange(e.list); userListForSpeakTo.Remove(ChatServiceClientManager.currentUserName); cbbSpeakTo.ItemsSource = userListForSpeakTo; } //登录窗口关闭处理方法 void login_Closed(object sender, EventArgs e) { ChatServiceClientManager.hasSignedIn = true; tbMyName.Text = loginWin.actualUserName; ChatServiceClientManager.currentUserName = loginWin.actualUserName; lstOnlineUser.ItemsSource = ChatServiceClientManager.onlineUserList; } //发送消息 private void btnSend_Click(object sender, RoutedEventArgs e) { //若用户未选择聊天对象,默认设置为对所有人说 if (cbbSpeakTo.SelectedValue == null) { cbbSpeakTo.SelectedIndex = 0; } //不能发送空消息 if (txtChatContentInput.Text.Trim() == string.Empty) { MessageBox.Show("不能发送空消息!", "提示", MessageBoxButton.OK); txtChatContentInput.Focus(); } else { tbChatContent.Text += ChatServiceClientManager.SendMessage(cbbSpeakTo.SelectedValue.ToString(), txtChatContentInput.Text); txtChatContentInput.Text = string.Empty; } } /// <summary> /// 列表点击事件,点击在线用户列表后自动设置说话对象为所点击对象 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void lstOnlineUser_SelectionChanged(object sender, SelectionChangedEventArgs e) { cbbSpeakTo.SelectedValue = lstOnlineUser.SelectedValue; } /// <summary> /// 聊天框按键事件,监听快捷键Ctrl+Enter发送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtChatContentInput_KeyDown(object sender, KeyEventArgs e) { ModifierKeys keys = Keyboard.Modifiers; if (e.Key == Key.Enter && keys == ModifierKeys.Control) { btnSend_Click(null, null); } } /// <summary> /// 聊天内容展示区域尺寸变化事件,用来实现自动滚动到最底 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tbChatContent_SizeChanged(object sender, SizeChangedEventArgs e) { scrollChatContentContainer.ScrollToVerticalOffset(tbChatContent.ActualHeight); } } }
最后在App.xaml.cs中的Application_Exit方法中添加:
private void Application_Exit(object sender, EventArgs e) { if (ChatServiceClientManager.hasSignedIn == true) { //用户关闭网页时通知服务端用户离开并关闭连接 ChatServiceClientManager.client.LeaveAsync(); ChatServiceClientManager.client.CloseAsync(); } }
终于大功告成,调试一下,程序启动后弹出登录框,登录后即可在聊天框内进行聊天了,可以自己多开几个浏览器进行测试。
下一步准备改进一下界面,并把现在传递的string类型的消息改为富文本的以支持用户设定的字体、字号等风格,当然没有表情也是不行的,回头再尝试做个窗口抖动什么的。
现在看程序写的比较乱而且基本是只追求功能实现,不知大牛们能不能给点改进的建议,应用点什么模式好使代码更优雅些,多谢支持!