This commit is contained in:
2018-12-23 15:04:23 +08:00
35 changed files with 1951 additions and 0 deletions
+32
View File
@@ -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";
}
}
+37
View File
@@ -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();
}
}
+194
View File
@@ -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
}
}
+50
View File
@@ -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;
}
}
}
+97
View File
@@ -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 //已停止
}
}
}
+12
View File
@@ -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) { }
}
}
+71
View File
@@ -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>
+32
View File
@@ -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; //新头像大小
}
}
+72
View File
@@ -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.
+36
View File
@@ -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")]
+81
View File
@@ -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);
}
}
}
}
+126
View File
@@ -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 //群组
}
}
+51
View File
@@ -0,0 +1,51 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2041
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interact", "Interact\Interact.csproj", "{CEF054AF-95B7-4B88-934E-02705FEEA554}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StormChatWPF", "StormChatWPF\StormChatWPF.csproj", "{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x64.ActiveCfg = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x64.Build.0 = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x86.ActiveCfg = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x86.Build.0 = Debug|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|Any CPU.Build.0 = Release|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x64.ActiveCfg = Release|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x64.Build.0 = Release|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x86.ActiveCfg = Release|Any CPU
{CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x86.Build.0 = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x64.ActiveCfg = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x64.Build.0 = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x86.ActiveCfg = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x86.Build.0 = Debug|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|Any CPU.Build.0 = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x64.ActiveCfg = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x64.Build.0 = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x86.ActiveCfg = Release|Any CPU
{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {16287CCA-1001-41B2-B0BA-101618B1DCFB}
EndGlobalSection
EndGlobal
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
+9
View File
@@ -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>
+17
View File
@@ -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
{
}
}
+87
View File
@@ -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("连接服务器失败!");
}
}//登录
}
}
+30
View File
@@ -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>
+37
View File
@@ -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);
}
}
}
+31
View File
@@ -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>
+83
View File
@@ -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界面
}
}
+55
View File
@@ -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")]
+120
View File
@@ -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>
+26
View File
@@ -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

+64
View File
@@ -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); }
}
}
}
+124
View File
@@ -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>
+41
View File
@@ -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