update readme

This commit is contained in:
2018-12-23 15:37:19 +08:00
38 changed files with 1953 additions and 15084 deletions
+53 -6
View File
@@ -1,5 +1,26 @@
# StormChat # StormChat
## 目录
* <a href="#intro">简介</a>
* <a href="#design">设计思路</a>
* <a href="#design-comunication">通信流程</a>
* <a href="#design-packet-struct">通信数据包结构</a>
* <a href="#env-config">环境配置</a>
* <a href="#env-config-dev">开发环境</a>
* <a href="#env-config-run">运行环境</a>
* <a href="#soft-config">程序配置</a>
* <a href="#soft-config-server">服务器端</a>
* <a href="#soft-config-client-go">客户端(Go</a>
* <a href="#soft-config-client-cs">客户端(C#</a>
* <a href="#build">如何编译</a>
* <a href="#build-server">服务器端</a>
* <a href="#build-client-go">客户端(Go</a>
* <a href="#build-client-cs">客户端(C#</a>
* <a href="#warning">注意事项</a>
* <a href="#dir-struct">目录结构</a>
* <a href="#usage-pic">使用截图</a>
<a name="intro"></a>
## 简介 ## 简介
&emsp;&emsp;前段时间心血来潮想学习最近的明星编程语言Golang。于是想做个聊天小程序实践一下。程序基于TCP协议通信,更详细的设计见<a href="#design">设计思路</a> &emsp;&emsp;前段时间心血来潮想学习最近的明星编程语言Golang。于是想做个聊天小程序实践一下。程序基于TCP协议通信,更详细的设计见<a href="#design">设计思路</a>
@@ -8,15 +29,19 @@
&emsp;&emsp;在朋友([@Billows](https://github.com/KaniuBillows))的鼓励下,我们决定用C#写一个客户端程序,正好他在学习WPF编程,于是界面采用了WPF编写。我负责后台数据交互,他负责前台界面逻辑。 &emsp;&emsp;在朋友([@Billows](https://github.com/KaniuBillows))的鼓励下,我们决定用C#写一个客户端程序,正好他在学习WPF编程,于是界面采用了WPF编写。我负责后台数据交互,他负责前台界面逻辑。
&emsp;&emsp;**C#客户端还使用到了JSON格式化库<a href="https://github.com/JamesNK/Newtonsoft.Json">Newtonsoft.Json</a>,特此说明。** &emsp;&emsp;**C#客户端还使用到了JSON格式化库<a href="https://github.com/JamesNK/Newtonsoft.Json">Newtonsoft.Json</a>,特此说明。**
<a name="design"></a> <a name="design"></a>
## 设计思路 ## 设计思路
<a name="design-comunication"></a>
### 通信流程 ### 通信流程
1. 发送方(客户端或者服务器)发送数据包; 1. 发送方(客户端或者服务器)发送数据包;
2. 接收方(服务器或者客户端)处理数据; 2. 接收方(服务器或者客户端)处理数据;
3. 如果需要反馈,接收方发送反馈数据包; 3. 如果需要反馈,接收方发送反馈数据包;
4. 发送方处理反馈数据(如果有)。 4. 发送方处理反馈数据(如果有)。
<a name="design-packet-struct"></a>
### 通信数据包结构 ### 通信数据包结构
数据通信基于TCP协议,每次通信的数据包应包含【包头域+数据域】。包头域总是JSON格式、UTF-8编码的字符串,数据域由包头决定,可以为空。通信数据包结构如图。 数据通信基于TCP协议,每次通信的数据包应包含【包头域+数据域】。包头域总是JSON格式、UTF-8编码的字符串,数据域由包头决定,可以为空。通信数据包结构如图。
@@ -32,27 +57,35 @@ Operation参数指定本次通信的请求。它决定了数据包的其他数
本来做了设计图表,不过第一次用starUML,画了一半才发现完全是鬼画桃符,没有掌握正确的作图方式,也没什么热情重新画了。因此这里不展示完整的设计文件了(本来也不完整),上面的链接是截取的关键部分(才知道starUML可以导出html文档)。 本来做了设计图表,不过第一次用starUML,画了一半才发现完全是鬼画桃符,没有掌握正确的作图方式,也没什么热情重新画了。因此这里不展示完整的设计文件了(本来也不完整),上面的链接是截取的关键部分(才知道starUML可以导出html文档)。
至于原文件...留着权当做个纪念,当作自己学习的见证吧(我也很无奈啊(* ̄︿ ̄))。实在是不嫌弃不怕被误导的话,也可以看看。文件是starUML3的设计文件(.mdj),需要<a href="http://staruml.io/">starUML</a>软件打开(付费的,不过可以免费使用)。这是<a href="design/stormchat.mdj" download="stormchat.mdj">设计文件</a>。 至于原文件...留着权当做个纪念,当作自己学习的见证吧(我也很无奈啊(* ̄︿ ̄))。实在是不嫌弃不怕被误导的话,也可以看看。文件是starUML3的设计文件(.mdj),需要<a href="http://staruml.io/">starUML</a>软件打开(付费的,不过可以免费使用)。这是<a href="design/stormchat.zip">设计文件</a>。
哦,还有服务器端的数据库结构,见文件stormchat-server/res/stormchat.sql。 哦,还有服务器端的数据库结构,见文件stormchat-server/res/stormchat.sql。
<a name="env-config"></a>
## 环境配置 ## 环境配置
1. ### 开发配置 <a name="env-config-dev"></a>
1. ### 开发环境
服务器端:Windows/Linux Golang 1.11 服务器端:Windows/Linux Golang 1.11
客户端-GoWindows x64C++Golang 1.11 客户端-GoWindows x64C++Golang 1.11
客户端-C#.NET4.0WPFNewtonsoft.Json for .NET4.0 客户端-C#Windows.NET4.0WPFNewtonsoft.Json for .NET4.0
2. ### 运行配置 <a name="env-config-run"></a>
2. ### 运行环境
服务器端:Linux/Windows, MySQL5.5+/MariaDB10.0+ 服务器端:Linux/Windows, MySQL5.5+/MariaDB10.0+
客户端:Windows 7以上 客户端:Windows10 x64(其他平台没测试过)
<a name="soft-config"></a>
## 程序配置 ## 程序配置
由于懒癌发作,一些参数设置只能在编译时指定好,没有运行中指定参数的功能。 由于懒癌发作,一些参数设置只能在编译时指定好,没有运行中指定参数的功能。
Go语言的部分(服务器程序和golang客户端程序)这块都在control.go文件中,C#客户端这边也没什么可配置的,也就是连接服务器的地址和端口。下面列出部分配置参数: Go语言的部分(服务器程序和golang客户端程序)这块都在control.go文件中,C#客户端这边也没什么可配置的,也就是连接服务器的地址和端口。下面列出部分配置参数:
<a name="soft-config-server"></a>
### 服务器端 ### 服务器端
* **serveMode** * **serveMode**
服务(后台)模式。如果此参数为true,日志输出到str_log_file参数指定的文件中,且Debug()函数将不输出内容。如果为false,日志输出和Debug()函数都将直接向控制台或终端输出。 服务(后台)模式。如果此参数为true,日志输出到str_log_file参数指定的文件中,且Debug()函数将不输出内容。如果为false,日志输出和Debug()函数都将直接向控制台或终端输出。
@@ -75,10 +108,12 @@ Go语言的部分(服务器程序和golang客户端程序)这块都在contro
* **str_db_conn_str** * **str_db_conn_str**
MySQL数据库连接字符串。 MySQL数据库连接字符串。
<a name="soft-config-client-go"></a>
### 客户端(Go ### 客户端(Go
* server_addr * server_addr
服务器地址和端口。 服务器地址和端口。
<a name="soft-config-client-cs"></a>
### 客户端(C# ### 客户端(C#
资源定义在StormChat解决方案,Interact项目的属性->资源中。 资源定义在StormChat解决方案,Interact项目的属性->资源中。
* RemoteServerAddr * RemoteServerAddr
@@ -88,6 +123,8 @@ Go语言的部分(服务器程序和golang客户端程序)这块都在contro
服务器端口。 服务器端口。
<a name="build"></a>
## 如何编译 ## 如何编译
首先安装git和golang,这一步请自行解决; 首先安装git和golang,这一步请自行解决;
@@ -98,6 +135,7 @@ Go语言的部分(服务器程序和golang客户端程序)这块都在contro
> `cd DIR/stormchat` > `cd DIR/stormchat`
<a name="build-server"></a>
1. ### 服务器端 1. ### 服务器端
#### 切换到服务器端工程目录: #### 切换到服务器端工程目录:
> `cd stormchat-server` > `cd stormchat-server`
@@ -144,22 +182,28 @@ Linux下执行下列命令以守护进程运行:
Windows请自行探索。 Windows请自行探索。
<a name="build-client-go"></a>
2. ### 客户端-Go 2. ### 客户端-Go
> `cd DIR/stormchat/stormchat-client-golang/src` > `cd DIR/stormchat/stormchat-client-golang/src`
> `go build` > `go build`
注意,**客户端运行时需要conctrl_x64.dll在运行目录**下,此文件在stormchat-client-golang/res/目录下。 注意,**客户端运行时需要conctrl_x64.dll在运行目录**下,此文件在stormchat-client-golang/res/目录下。
<a name="build-client-cs"></a>
3. ### 客户端-C# 3. ### 客户端-C#
Visual Studio 2015以上版本打开项目,直接编译即可。 Visual Studio 2015以上版本打开项目,直接编译即可。
<a name="warning"></a>
## 注意事项 ## 注意事项
* 没有设计注册账户的API,只能在强插数据库。emmmm,这个坑懒得填了。 * 没有设计注册账户的API,只能在强插数据库。emmmm,这个坑懒得填了。
* 服务器端的日志文件不会自动清除(反正也没什么日志要写)。 * 服务器端的日志文件不会自动清除(反正也没什么日志要写)。
* 其他的,想到再补充。 * 其他的,想到再补充。
<a name="dir-struct"></a>
## 目录结构(主要部分) ## 目录结构(主要部分)
<pre> <pre>
stormchat stormchat
@@ -171,7 +215,7 @@ stormchat
├─design //设计文件 ├─design //设计文件
│ message.png │ message.png
│ operations.svg │ operations.svg
│ stormchat.mdj //starUML设计文件 │ stormchat.zip //starUML设计文件
├─stormchat-server //服务器端工程目录 ├─stormchat-server //服务器端工程目录
@@ -224,6 +268,9 @@ stormchat
└─StormChatWPF //数据表现和用户交互模块* └─StormChatWPF //数据表现和用户交互模块*
</pre> </pre>
<a name="usage-pic"></a>
## 使用截图 ## 使用截图
### Golang客户端 ### Golang客户端
<img src="pic/client-login.png"/> <img src="pic/client-login.png"/>
-15078
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Interact
{
//Json数据的字段访问字符串
public static class AttrNames
{
//基本信息
public const string Token = "Token";
public const string Operation = "Operation";
//处理结果信息
public const string Error = "Error";
//获取用户信息
public const string User = "User";
public const string NickName = "NickName";
public const string Motto = "Motto";
public const string UGroup = "UGroup";
public const string Photo = "Photo";
public const string Total = "Total";
public const string Count = "Count";
//消息
public const string When = "When";
public const string From = "From";
public const string To = "To";
}
}
@@ -0,0 +1,37 @@
using System;
using System.Net.Sockets;
using System.Collections.Concurrent;
using System.Threading;
using System.Collections.Generic;
//定义StormClient私有字段
namespace Interact
{
public partial class StormClient
{
//TCP客户端
private static TcpClient tcpClient;
//消息发送队列
private static BlockingCollection<Packet> sendQueue;
//数据读取线程
private static Thread readLoopThread;
//数据发送线程
private static Thread sendLoopThread;
//某些请求需要持续的等待服务器返回数据包,然后将所有数据包处理后提供给用户,
// 此字段用于暂存这些未接收完全的数据包。它的key是对应请求的Token字串。
private static Dictionary<string, object> workingDictionary;
//属性控制字段,任何地方都不应该修改它们
//发送消息时是否要求服务器返回处理结果。由DoesSendMessageReturn属性控制,不要直接修改这个字段
private static volatile bool doesSendMessageReturnResult = false;
//当前连接状态。由Status属性控制,不要直接修改这个字段
private static volatile ClientStatus clientStatus = ClientStatus.Uninitialized;
//线程安全-锁
//更改连接状态锁
private static object statusLocker = new object();
//"发送消息时是否要求服务器返回处理结果"Falg锁
private static object doesSendMessageReturnLocker = new object();
}
}
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Drawing;
using System.IO;
//StormClient对数据的处理相关内容
namespace Interact
{
public partial class StormClient
{
//处理连接中断异常。
private static void HandleConnectionBroken(Exception ex)
{
lock (statusLocker)
{
if (Status != ClientStatus.Running)
return;
statusLocker = ClientStatus.Stopped;
tcpClient.Close();
OnDisconnect?.Invoke(ex);
}
}
#region
//根据包头信息处理数据
private static void HandleAll(byte[] head, byte[] data)
{
string headStr = Encoding.UTF8.GetString(head);
JObject jsonObj = (JObject)JsonConvert.DeserializeObject(headStr);
switch (jsonObj[AttrNames.Operation].ToString())
{
case Operations.Panic:
HandlePanic(GetResultHead(jsonObj));
break;
case Operations.Login:
HandleLoginResult(jsonObj, data);
break;
case Operations.TransMessage:
HandleMessage(jsonObj, data);
break;
case Operations.SendMessage:
HandleSendMessageResult(GetResultHead(jsonObj));
break;
case Operations.Offline:
HandlePanic(GetResultHead(jsonObj));
break;
case Operations.UpdateUserInfo:
HandleUpdateUserInfoDone(GetResultHead(jsonObj));
break;
case Operations.GetUsers:
HandleGetUsersPack(jsonObj, data);
break;
default:
break;
}
}
//提取基本结果包头结构
private static ResultHead GetResultHead(JObject head)
{
ResultHead resultHead = new ResultHead
{
Token = head[AttrNames.Token].ToString(),
Operation = head[AttrNames.Operation].ToString(),
};
if (head[AttrNames.Error] != null)
resultHead.Error = head[AttrNames.Error].ToString();
return resultHead;
}
//处理服务器异常断开连接消息
private static void HandlePanic(ResultHead head)
{
SetStatus(ClientStatus.Stopped);
OnPanic?.Invoke(head);
}
//处理登录反馈消息
private static void HandleLoginResult(JObject head, byte[] data)
{
//提取结果包头
ResultHead resultHead = GetResultHead(head);
//发起登录完成事件
if (head[AttrNames.Error].ToString() != "")
{
OnLoginDone?.Invoke(resultHead, null);
return;
}
User user = new User
{
Name = head[AttrNames.User].ToString(),
NickName = head[AttrNames.NickName].ToString(),
Motto = head[AttrNames.Motto].ToString(),
Group = (UserGroup)Enum.Parse(typeof(UserGroup), head[AttrNames.UGroup].ToString()),
Photo = User.DefaultPhoto
};
if (int.Parse(head[AttrNames.Photo].ToString()) > 0)
{
MemoryStream ms = new MemoryStream(data);
user.Photo = Image.FromStream(ms);
} //用户头像数据
OnLoginDone?.Invoke(resultHead, user);
}
//收到消息
private static void HandleMessage(JObject head, byte[] data)
{
//提取结果包头
ResultHead resultHead = GetResultHead(head);
Message message = new Message()
{
When = DateTime.Parse(head[AttrNames.When].ToString()),
From = User.FromUserName(head[AttrNames.From].ToString()),
To = User.Me,
Text = Encoding.UTF8.GetString(data)
};
OnMessage?.Invoke(message);
}
//发送消息执行结果
private static void HandleSendMessageResult(ResultHead head)
{
OnSendMessageDone?.Invoke(head);
}
//处理掉线、强制登出
private static void HandleOffiline(ResultHead head)
{
SetStatus(ClientStatus.Stopped);
OnOffline?.Invoke(head);
}
//处理更改信息结果
private static void HandleUpdateUserInfoDone(ResultHead head)
{
OnUpdateUserInfoDone?.Invoke(head);
}
//处理获取用户列表的不连续返回包。当所有包接收完毕后返回给客户
private static void HandleGetUsersPack(JObject head, byte[] data)
{
ResultHead result = GetResultHead(head);
if (head[AttrNames.Error].ToString() != string.Empty)
{
OnGetUserListDone?.Invoke(result, new User[0]);
return;
} //获取失败
else if (int.Parse(head[AttrNames.Count].ToString()) <= 0)
{
User[] us;
if (workingDictionary.ContainsKey(result.Token))
us = workingDictionary[result.Token] as User[];
else
us = new User[0];
User.Users = us;
OnGetUserListDone?.Invoke(result, us);
return;
} //获取完成
//获取用户数据
User user = new User
{
Name = head[AttrNames.User].ToString(),
NickName = head[AttrNames.NickName].ToString(),
Motto = head[AttrNames.Motto].ToString(),
Group = (UserGroup)Enum.Parse(typeof(UserGroup), head[AttrNames.UGroup].ToString()),
Photo = User.DefaultPhoto
}; //基础数据
if (int.Parse(head[AttrNames.Photo].ToString()) > 0)
{
MemoryStream ms = new MemoryStream(data);
user.Photo = Image.FromStream(ms);
} //头像数据
//加入临时用户列表缓存
User[] users;
int total = int.Parse(head[AttrNames.Total].ToString()); //总用户数
int count = int.Parse(head[AttrNames.Count].ToString()); //已接收用户数
if (count > total)
return;
if (workingDictionary.ContainsKey(head[AttrNames.Token].ToString()))
users = workingDictionary[result.Token] as User[];
else
users = new User[total];
users[count - 1] = user;
workingDictionary[result.Token] = users;
}
#endregion
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
//StormClient读取数据相关内容
namespace Interact
{
public partial class StormClient
{
// 数据读取线程,从tcp连接读取数据
private static void ReadLoop()
{
NetworkStream stream = StormClient.tcpClient.GetStream();
try
{
while (StormClient.Status == ClientStatus.Running)
{
byte[] head = ReadDataWithLength(stream);
byte[] data = ReadDataWithLength(stream);
HandleAll(head, data);
}
}
catch (System.IO.IOException ex)
{
HandleConnectionBroken(ex);
}
catch (ObjectDisposedException ex)
{
HandleConnectionBroken(ex);
}
}
// 从网络流读取数据
private static byte[] ReadDataWithLength(NetworkStream stream)
{
byte[] lenBuffer = new byte[4]; //数据长度
//获取数据长度
stream.Read(lenBuffer, 0, 4);
int length = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenBuffer, 0));
if (length <= 0)
return null;
//读取数据
byte[] data = new byte[length];
stream.Read(data, 0, length);
return data;
}
}
}
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Net;
using System.Net.Sockets;
//StormClient发送数据相关内容
namespace Interact
{
public partial class StormClient
{
// 消息写入线程,向tcp连接写入数据
private static void SendLoop()
{
Packet packet = null;
NetworkStream stream = tcpClient.GetStream();
//json序列化时忽略值为null的项
JsonSerializerSettings setting = new JsonSerializerSettings();
setting.NullValueHandling = NullValueHandling.Ignore;
try
{
//阻塞式的从发送队列中取出数据
foreach (Packet i in sendQueue.GetConsumingEnumerable())
{
packet = i; //为了在代码块外引用i
//结束循环
if (StormClient.Status != ClientStatus.Running)
break;
//向tcp连接写数据
byte[] lenBuffer; //数据长度缓存
byte[] headData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(packet.Head, setting));
//写包头
lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(headData.Length));
stream.Write(lenBuffer, 0, 4);
stream.Write(headData, 0, headData.Length);
//写数据
int length = 0;
if (packet.Data != null)
length = packet.Data.Length;
lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(length));
stream.Write(lenBuffer, 0, 4);
if (length > 0)
stream.Write(packet.Data, 0, packet.Data.Length);
//发送成功回调
ResultHead result = new ResultHead
{
Token = packet.Head.Token,
Operation = packet.Head.Operation,
Error = ""
};
packet.CallBack?.Invoke(result);
}
}
catch (System.IO.IOException ex)
{
HandleConnectionBroken(ex);
}
catch (ObjectDisposedException ex)
{
HandleConnectionBroken(ex);
}
finally
{
List<Packet> packets = new List<Packet>(sendQueue.Count + 1);
if (packet != null)
packets.Add(packet);
while (sendQueue.TryTake(out packet))
{
if (packet != null)
packets.Add(packet);
}
foreach (Packet p in packets)
{
//发送失败回调
ResultHead result = new ResultHead
{
Token = packet.Head.Token,
Operation = packet.Head.Operation,
Error = "Client has stopped running."
};
packet.CallBack?.Invoke(result);
}
}
}
//发送数据
private static bool Send(Packet packet)
{
if (sendLoopThread == null || !sendLoopThread.IsAlive)
return false;
//加入数据发送队列等待
sendQueue.Add(packet);
return true;
}
}
}
+260
View File
@@ -0,0 +1,260 @@
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace Interact
{
//连接中断
public delegate void ConnectionBrokenHandler(Exception ex);
//通用请求结果处理
public delegate void ResultHandler(ResultHead head);
//消息到达事件处理
public delegate void MessageHandler(Message msg);
//登录反馈到达
public delegate void LoginDoneHandler(ResultHead head, User user);
//获取用户列表结果处理
public delegate void GetUserListDoneHandler(ResultHead head, User[] users);
/// <summary>
/// 与服务器进行数据通信,并向外部提供一系列通信接口
/// </summary>
public static partial class StormClient
{
/// <summary>
/// 连接中断事件。注意,此事件并不保证连接中断时总是触发,例如主动关闭连接、登出操作
/// </summary>
public static event ConnectionBrokenHandler OnDisconnect;
/// <summary>
/// 服务器异常消息处理事件
/// </summary>
public static event ResultHandler OnPanic;
/// <summary>
/// 服务器强制注销登陆事件
/// </summary>
public static event ResultHandler OnOffline;
/// <summary>
/// 消息到达事件
/// </summary>
public static event MessageHandler OnMessage;
/// <summary>
/// 发送消息处理完成反馈事件(仅在DoesSendMessageReturn为true时有效)
/// </summary>
public static event ResultHandler OnSendMessageDone;
/// <summary>
/// 更新用户信息结果处理事件
/// </summary>
public static event ResultHandler OnUpdateUserInfoDone;
/// <summary>
/// 登录结果处理事件
/// </summary>
public static event LoginDoneHandler OnLoginDone;
/// <summary>
/// 获取用户列表结果处理事件
/// </summary>
public static event GetUserListDoneHandler OnGetUserListDone;
//当前连接状态
public static ClientStatus Status
{
get { lock (statusLocker) { return clientStatus; } }
}
//发送消息时是否要求服务器返回处理结果
public static bool DoesSendMessageReturn
{
get { lock (doesSendMessageReturnLocker) { return doesSendMessageReturnResult; } }
set { lock (doesSendMessageReturnLocker) { doesSendMessageReturnResult = value; } }
}
static StormClient()
{
tcpClient = new TcpClient();
sendQueue = new BlockingCollection<Packet>();
workingDictionary = new Dictionary<string, object>();
SetStatus(ClientStatus.Uninitialized);
}
//设置当前连接状态
internal static void SetStatus(ClientStatus value)
{
lock (statusLocker) { clientStatus = value; }
}
/// <summary>
/// 初始化客户端,连接服务器。如果成功则启动数据接收和发送线程。
/// 注意,当网络不稳定时此方法可能会阻塞。
/// </summary>
/// <returns>如果已连接到服务器返回true,否则返回false。</returns>
public static bool Initialize()
{
if (StormClient.tcpClient.Connected)
return true;
try
{
StormClient.tcpClient.Connect(Interact.Properties.Resources.RemoteServerAddr, int.Parse(Interact.Properties.Resources.RemoteServerPort));
}
catch (Exception)
{
return false;
}
workingDictionary?.Clear(); //清空临时记录
if (sendLoopThread != null)
sendLoopThread.Abort();
if (readLoopThread != null)
readLoopThread.Abort();
//启动数据收发线程
sendLoopThread = new Thread(new ThreadStart(SendLoop));
readLoopThread = new Thread(new ThreadStart(ReadLoop));
sendLoopThread.IsBackground = readLoopThread.IsBackground = true;
SetStatus(ClientStatus.Running);
sendLoopThread.Start();
readLoopThread.Start();
return true;
}
/// <summary>
/// 登录。此操作为异步操作,服务器返回结果后产生OnLoginDone事件。
/// </summary>
/// <param name="user">用户名</param>
/// <param name="password">密码</param>
/// <param name="callback">数据发送完毕回调函数。注意,结果处理回调请设置OnLoginDone事件</param>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
public static bool QueueLogin(string user, string password, Action<ResultHead> callback = null)
{
JsonLogInfoHead logInfo = new JsonLogInfoHead()
{
Token = Guid.NewGuid().ToString(),
Operation = Operations.Login,
User = user,
Pwd = password
};
Packet packet = new Packet
{
Head = logInfo,
Data = null,
CallBack = callback
};
return Send(packet);
}
/// <summary>
/// 请求发送一条消息。异步,此方法将请求放入请求队列后返回。
/// </summary>
/// <param name="text">要发送的消息文本</param>
/// <param name="to">消息接收者</param>
/// <param name="callback">数据发送完毕时回调函数。</param>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
public static bool QueueSendMessage(Message message, Action<ResultHead> callback = null)
{
JsonSendMessageHead jsonObj = new JsonSendMessageHead()
{
Token = "",
Operation = Operations.SendMessage,
To = message.To.Name,
NeedResult = DoesSendMessageReturn ? "1" : "0"
};
Packet packet = new Packet
{
Head = jsonObj,
Data = Encoding.UTF8.GetBytes(message.Text),
CallBack = callback
};
return Send(packet);
}
/// <summary>
/// 请求登出。异步,此方法将请求放入请求队列后返回。
/// </summary>
/// <param name="callback">数据发送完毕时回调函数。</param>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
public static bool QueueLogout(Action<ResultHead> callback = null)
{
BaseHead jsonObj = new BaseHead()
{
Token = "",
Operation = Operations.Logout,
};
Packet packet = new Packet
{
Head = jsonObj,
Data = null,
CallBack = callback
};
bool success = Send(packet);
SetStatus(ClientStatus.Stopped);
return success;
}
/// <summary>
/// 请求用户列表。异步,此方法将请求放入请求队列后返回。
/// </summary>
/// <param name="callback">数据发送完毕时回调函数。</param>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
public static bool QueueGetUserList(Action<ResultHead> callback = null)
{
BaseHead jsonObj = new BaseHead()
{
Token = "",
Operation = Operations.GetUsers,
};
Packet packet = new Packet
{
Head = jsonObj,
Data = null,
CallBack = callback
};
return Send(packet);
}
/// <summary>
/// 请求更新当前登录用户信息。异步,此方法将请求放入请求队列后返回。
/// </summary>
/// <param name="userInfo">新的用户信息,如果某项值为null,则不改变该项。</param>
/// <param name="callback">数据发送完毕时回调函数。</param>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
public static bool QueueUpdateUserInfo(UserInfo userInfo, Action<ResultHead> callback = null)
{
byte[] photoData = null; //头像数据
//将头像数据转换到字符数组
if (userInfo.Photo != null)
{
MemoryStream memStream = new MemoryStream();
userInfo.Photo.Save(memStream, userInfo.Photo.RawFormat);
photoData = new byte[memStream.Length];
memStream.Seek(0, SeekOrigin.Begin);
memStream.Read(photoData, 0, photoData.Length);
}
//构建数据包
JsonUserInfoHead jsonObj = new JsonUserInfoHead()
{
Token = Guid.NewGuid().ToString(),
Operation = Operations.UpdateUserInfo,
NickName = userInfo.NickName,
Password = userInfo.Password,
Motto = userInfo.Motto,
Photo = userInfo.Photo == null ? 0 : photoData.Length
};
Packet packet = new Packet
{
Head = jsonObj,
Data = photoData,
CallBack = callback
};
return Send(packet);
}
/// <summary>
/// 客户端连接状态
/// </summary>
public enum ClientStatus
{
Uninitialized, //未启动
Running, //运行中
Stopped //已停止
}
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Interact
{
public class ConnectionBrokenException : Exception
{
public ConnectionBrokenException(string message) : base(message) { }
}
}
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CEF054AF-95B7-4B88-934E-02705FEEA554}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Interact</RootNamespace>
<AssemblyName>Interact</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>.\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Client.cs" />
<Compile Include="Exception.cs" />
<Compile Include="Client-Fields.cs" />
<Compile Include="Client-Handle.cs" />
<Compile Include="jsonObject.cs" />
<Compile Include="Message.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Client-Read.cs" />
<Compile Include="Client-Send.cs" />
<Compile Include="AttrNames.cs" />
<Compile Include="User.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Interact
{
//定义一则用户接收到的消息
public class Message
{
public Message() { }
public Message(string text, User Receiver)
{
When = DateTime.Now;
From = User.Me;
To = Receiver;
Text = text;
}
/*
public const UInt32 MaxHeadLength = 4096; //最大包头长度
public const UInt32 MaxMessageLength = 0x6400000; //最大数据长度
*/
public DateTime When; //服务器接收时间
public User From; //消息发送者
public User To; //消息接收者
public string Text; //消息内容
//将消息保存到数据库(保留)
protected bool Save() { return false; }
}
//从服务器接收的原始数据封包
internal class Packet
{
public BaseHead Head; //包头
public byte[] Data; //包内容
public Action<ResultHead> CallBack; //数据发送完成的回调函数。注意,此时仅保证数据已发送,
//服务器可能并没有处理完成请求
}
//允许请求的操作列表。具体定义见设计文档
public static class Operations
{
public const string Panic = "Panic";
public const string TransMessage = "TransMessage";
public const string SendMessage = "SendMessage";
public const string Login = "Login2";
public const string Logout = "Logout";
public const string Offline = "Offline";
public const string UpdateUserInfo = "UpdateUserInfo";
public const string GetUsers = "GetUsers";
}
#region
//包头基本结构
public class BaseHead
{
//请求ID,由发送方定义的随机字符串。如果接受方有返回数据应包含相同的Token
public string Token;
//操作类型,决定数据的其他内容
public string Operation;
}
// 接受方返回的执行结果反馈包头,包含错误文本
public class ResultHead : BaseHead
{
//错误文本,为空则执行成功
public string Error;
}
//获取单一用户信息结果包头
#endregion
}
Binary file not shown.
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Interact")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Interact")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cef054af-95b7-4b88-934e-02705feea554")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Interact.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Interact.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to mattuy.top.
/// </summary>
internal static string RemoteServerAddr {
get {
return ResourceManager.GetString("RemoteServerAddr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 3727.
/// </summary>
internal static string RemoteServerPort {
get {
return ResourceManager.GetString("RemoteServerPort", resourceCulture);
}
}
}
}
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RemoteServerAddr" xml:space="preserve">
<value>mattuy.top</value>
</data>
<data name="RemoteServerPort" xml:space="preserve">
<value>3727</value>
</data>
</root>
+63
View File
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace Interact
{
//用户
public class User
{
//根据用户名匹配用户。如果用户不存在,则返回一个仅用户名有效的实例
internal static User FromUserName(string userName)
{
if(User.Users != null)
{
foreach (User user in User.Users)
{
if (user.Name == userName)
return user;
}
}
User unidentifiedUser = new User
{
Name = userName,
NickName = "",
Motto = "",
Group = UserGroup.User,
Photo = User.DefaultPhoto
};
return unidentifiedUser;
}
internal static User[] Users; //用户列表。仅Interact内部访问
public static User Me; //当前用户
public static Image DefaultPhoto; //默认头像
public string Name; //用户名
public string NickName; //昵称
public string Motto; //个性签名
public UserGroup Group; //用户组
public Image Photo; //头像
}
//更新用户信息时新的用户信息。如果项值为null则不更改相应的信息
public class UserInfo
{
public string Password = null; //新密码
public string NickName = null; //新昵称
public string Motto = null; //新签名
public Image Photo = null; //新头像
}
//用户类型
public enum UserGroup
{
User, //普通用户
Vip, //会员
Admin, //管理员
Group //群组
}
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//定义Json序列化与反序列化时使用的数据结构,仅内部使用
namespace Interact
{
//登录信息包头
internal class JsonLogInfoHead : BaseHead
{
public string User;
public string Pwd;
}
//发送消息包头
internal class JsonSendMessageHead : BaseHead
{
public string To; //消息接收者用户名
public string NeedResult = "0"; //是否要求服务器返回处理结果。否传“0”
}
//更新用户信息包头
internal class JsonUserInfoHead : BaseHead
{
public string NickName = null;
public string Password = null;
public string Motto = null;
public int Photo = 0; //新头像大小
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
@@ -0,0 +1,9 @@
<Application x:Class="StormChatWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StormChatWPF"
StartupUri="LogWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace StormChatWPF
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
}
}
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Interact;
using System.Drawing;
using System.Windows;
namespace StormChatWPF
{
class Chat
{
public static List<User> ContactsList;//联系人集合
public static Chat chat=new Chat();
private Chat()
{
StormClient.OnLoginDone += OnHaveLogin;
StormClient.OnGetUserListDone += OnGetContactsList;
StormClient.OnMessage +=OnMessage;
}
internal static User CurrentContact { get; set; }//设置当前联系人对象
/// <summary>
/// 获取联系人列表(完成后打开主窗体)
/// </summary>
private void OnGetContactsList(ResultHead head, User[] users)
{
if (head.Error != "")
{
MessageBox.Show("无法获取联系人列表");
return;
}
App.Current.Dispatcher.Invoke(
(Action)delegate ()
{
ContactsList = new List<User>(users);
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
LogWindow.Instence.Close();
});
}
private void OnHaveLogin(ResultHead head, User user)
{
if (head.Error == "")
{
User.Me = user;
StormClient.QueueGetUserList();
}
else
{
MessageBox.Show("请核对账号密码!");
}
}//登录完成
private void OnMessage(Message message)
{
if (MainWindow.Instence != null)
{
MainWindow.Instence.ShowMessage(message);
}
}//接受到新消息
internal void SendMessage(string text,User target)
{
Message msg = new Message(text,target);
Action<ResultHead> f = delegate (ResultHead head)
{
MainWindow.Instence.ShowMessage(msg);
};
StormClient.QueueSendMessage(msg,f);
}//发送消息
internal static void Log(string user, string password)
{
if (StormClient.Initialize())
{
if (!StormClient.QueueLogin(user, password))
{
MessageBox.Show("登录失败");
}
}
else
{
MessageBox.Show("连接服务器失败!");
}
}//登录
}
}
@@ -0,0 +1,30 @@
<Window x:Class="StormChatWPF.LogWindow"
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"
xmlns:local="clr-namespace:StormChatWPF"
mc:Ignorable="d"
Title="WelCome" Height="450" Width="800"
Icon="UI/Resources/闪电.png"
WindowStartupLocation="CenterScreen">
<Grid Height="418" VerticalAlignment="Stretch" Margin="2,1,1,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="150*"/>
<RowDefinition Height="150*"/>
<RowDefinition Height="150*"/>
</Grid.RowDefinitions>
<Image x:Name="LogBackground" Grid.ColumnSpan="4" Height="268" Margin="10,10,10,0" Grid.RowSpan="2" VerticalAlignment="Top" Source="pack://application:,,,/UI/Resources/登录界面底.png"/>
<Button x:Name="Login_button" Content="LogIn" Click="Login_button_Click" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="2" Margin="160.6,69.6,160.6,44.6" Width="70" Height="25" HorizontalAlignment="Center"/>
<TextBox x:Name="AccountBox" TextWrapping="Wrap" Text="201701110203" Grid.Row="2" TextAlignment="Left" Margin="137.6,4.6,137.6,112.6" Grid.Column="1" Grid.ColumnSpan="2" Height="22" Width="122" HorizontalAlignment="Center" BorderThickness="0,0,0,1" VerticalAlignment="Top" />
<PasswordBox x:Name="passwordBox" Password="1233211234567" Margin="135,35,135,0" Grid.Column="1" Grid.Row="2" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="22" Width="122" HorizontalAlignment="Center" BorderThickness="0,0,0,1" HorizontalContentAlignment="Left"/>
<Label x:Name="Account" Content="Account:" Grid.Column="1" Margin="0,138.8,70.2,0" Grid.Row="1" VerticalAlignment="Top" RenderTransformOrigin="0.415,0.379" Height="24" FontFamily="Comic Sans MS" FontStyle="Italic" Grid.RowSpan="2" HorizontalContentAlignment="Center" HorizontalAlignment="Right" Width="60"/>
<Label x:Name="Password" Content="Password:" Grid.Column="1" Margin="0,31.6,65.2,0" Grid.Row="2" VerticalAlignment="Top" FontFamily="Comic Sans MS" FontStyle="Italic" Height="24" HorizontalContentAlignment="Center" HorizontalAlignment="Right" Width="70"/>
</Grid>
</Window>
@@ -0,0 +1,37 @@
using Interact;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace StormChatWPF
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class LogWindow : Window
{
public static LogWindow Instence;
public LogWindow()
{
InitializeComponent();
Instence = this;
}
private void Login_button_Click(object sender, RoutedEventArgs e)
{
Chat.Log(AccountBox.Text,passwordBox.Password);
}
}
}
@@ -0,0 +1,31 @@
<Window x:Class="StormChatWPF.MainWindow"
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"
xmlns:local="clr-namespace:StormChatWPF"
mc:Ignorable="d"
Title="StormChat" Height="450" Width="800"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60*"/>
<RowDefinition Height="300*"/>
<RowDefinition Height="90*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="UsersList" Margin="0,0,0,0" IsEnabled="True" SelectionChanged="UsersList_SelectionChanged" Grid.Row="1" Grid.RowSpan="2"/>
<TextBox x:Name="InputBox" Margin="0,0,0,0" TextWrapping="Wrap" Text="" Grid.Row="2" Grid.ColumnSpan="3" Grid.Column="1" />
<Button Content="Send" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Bottom" Width="75" Click="Button_Click" Height="20" Grid.Row="2" Grid.Column="3"/>
<ScrollViewer Grid.ColumnSpan="3" Grid.Column="1" Margin="0,0,0,0" Grid.Row="1" >
<ScrollViewer.Content>
<StackPanel x:Name="OutBox" Margin="0,0,0,0" Grid.ColumnSpan="3" Grid.Column="1" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Hidden"/>
</ScrollViewer.Content>
</ScrollViewer>
</Grid>
</Window>
@@ -0,0 +1,83 @@
using Interact;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace StormChatWPF
{
/// <summary>
/// UserWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public static MainWindow Instence;
public MainWindow()
{
InitializeComponent();
LoadContactsList();
Instence = this;
}
private void LoadContactsList()
{
if (Chat.ContactsList.Any())
{
foreach (var user in Chat.ContactsList)
{
UsersList.Items.Add(user.NickName);
}
}
}//向联系人列表listview中添加元素
private void UsersList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (UsersList.SelectedItem != null)
{
Chat.CurrentContact = Chat.ContactsList[UsersList.SelectedIndex];
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Chat.CurrentContact == null)
{
MessageBox.Show("请选择联系人!");
return;
}
if (InputBox.Text!="")
{
Chat.chat.SendMessage(InputBox.Text, Chat.CurrentContact);
InputBox.Text = "";
}
}
internal void ShowMessage(Message message)
{
if (message.To == User.Me)
{
App.Current.Dispatcher.Invoke(
delegate ()
{
OutBox.Children.Add(new ChatBubble(message,HorizontalAlignment.Left));
});
}//接受到的的消息
else
{
App.Current.Dispatcher.Invoke(
delegate ()
{
OutBox.Children.Add(new ChatBubble(message,HorizontalAlignment.Right));
});
}//发送的的消息
}//将消息展现于UI界面
}
}
@@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("StormChatWPF")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StormChatWPF")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
//若要开始生成可本地化的应用程序,请设置
//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
//例如,如果您在源文件中使用的是美国英语,
//使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消
//对以下 NeutralResourceLanguage 特性的注释。 更新
//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //主题特定资源词典所处位置
//(未在页面中找到资源时使用,
//或应用程序资源字典中找到时使用)
ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
//(未在页面中找到资源时使用,
//、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
// 方法是按如下所示使用“*”: :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace StormChatWPF.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.8.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@@ -0,0 +1,64 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace StormChatWPF
{
public class ScrollViewerExtensions
{
public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached("AlwaysScrollToEnd", typeof(bool), typeof(ScrollViewerExtensions), new PropertyMetadata(false, AlwaysScrollToEndChanged));
private static bool _autoScroll;
private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scroll = sender as ScrollViewer;
if (scroll != null)
{
bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue;
if (alwaysScrollToEnd)
{
scroll.ScrollToEnd();
scroll.ScrollChanged += ScrollChanged;
// scroll.SizeChanged += Scroll_SizeChanged;
}
else { scroll.ScrollChanged -= ScrollChanged; /*scroll.ScrollChanged -= ScrollChanged; */}
}
else { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); }
}
        //private static void Scroll_SizeChanged(object sender, SizeChangedEventArgs e)
        //{
        //    ScrollViewer scroll = sender as ScrollViewer;
        //    if (scroll == null) { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); }
        //    double d = scroll.ActualHeight + scroll.ViewportHeight + scroll.ExtentHeight;
        //    scroll.ScrollToVerticalOffset(d);
        //}
        public static bool GetAlwaysScrollToEnd(ScrollViewer scroll)
{
if (scroll == null) { throw new ArgumentNullException("scroll"); }
return (bool)scroll.GetValue(AlwaysScrollToEndProperty);
}
public static void SetAlwaysScrollToEnd(ScrollViewer scroll, bool alwaysScrollToEnd)
{
if (scroll == null) { throw new ArgumentNullException("scroll"); }
scroll.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd);
}
private static void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scroll = sender as ScrollViewer;
if (scroll == null) { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); }
if (e.ExtentHeightChange == 0) { _autoScroll = scroll.VerticalOffset == scroll.ScrollableHeight; }
if (_autoScroll && e.ExtentHeightChange != 0) { scroll.ScrollToVerticalOffset(scroll.ExtentHeight); }
}
}
}
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>StormChatWPF</RootNamespace>
<AssemblyName>StormChatWPF</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject>StormChatWPF.App</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Chat.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="ScrollViewer.cs" />
<Compile Include="UI\ChatBubble.cs" />
<Page Include="LogWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="LogWindow.xaml.cs">
<DependentUpon>LogWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="MainWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Interact\Interact.csproj">
<Project>{cef054af-95b7-4b88-934e-02705feea554}</Project>
<Name>Interact</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="UI\Resources\登录界面底.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="UI\Resources\闪电.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="UI\Resources\聊天气泡.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,41 @@
using Interact;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace StormChatWPF
{
/// <summary>
/// 聊天气泡
/// </summary>
class ChatBubble:Label
{
TextBlock text = new TextBlock()
{
MaxWidth = 400,
MinWidth = 10,
MinHeight = 20,
TextWrapping = TextWrapping.Wrap,
FontFamily = new FontFamily("Comic Sans MS"),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom
};
/// <summary>
///
/// </summary>
/// <param name="msg">传入消息体</param>
/// <param name="alignment">设置水平对齐方式,参数为HorizontalAlignment枚举类型</param>
public ChatBubble(Message msg,HorizontalAlignment alignment)
{
Background = new ImageBrush()
{
ImageSource=new BitmapImage(new Uri("pack://application:,,,/UI/Resources/聊天气泡.png"))
};
HorizontalAlignment = alignment;
text.Text = msg.Text;
Content = text;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB