仿QQ的聊天程序,其服务端使用WebApi与WebSocket为技术核心,配和EntityFramework框架利用SqlServer数据库进行储存的Asp.Net应用,而客户端是利用Wpf框架做UI层的桌面应用,与服务器进行实时通信达到数据传输的功能
IDE: Visual Studio 2022,Blend For Visual Studio 2022
NET框架: Net 6.0
开发语言: C#
IIS版本: 8.5
项目框架: Windows Presentation Foundation + ASP.NET Core WebApi
开发模式: C/S
1.登陆账号,包括注册和找回密码
2.添加好友并且有好友栏
3.与选择好友进行实时聊天
4.可以删除好友
5.公共聊天室
6.可以对对好友进行搜索,并且添加好友的时候可以进行搜索
用于点对点聊天或者聊天室聊天
为了防止客户端对数据库直接注入,所有使用WebApi作为中间媒介进行数据的转存,对外提供了User接口来做到登录,注册和重置,而在客户端方面,编写了UserHelper类作为DAL层的内容,而为了实现发送消息的功能,采用了WebSocket技术,因为在公网环境下,不可能做到一对一的Socket绑定,所以使用服务器作为媒介,客户端将消息发送给服务器,经服务器转发给目标
而对于客户端,也会开启一个监听线程去接收来自服务器的消息,经过CallBack进行分类显示.而聊天室的原理也就是存在一个UID为”HOST”的用户,而服务器检测到发送给HOST的消息后,会将该消息转发给除发送者以外的所有用户,这样就是聊天室的原理实现。
而在聊天信息的储存方面,通过一个叫做Bucket的结构进行储存,当用户发送信息的时候会在Bucket中存入一个Record对象,这个对象储存了信息发送者,信息接收者和信息主体,而监听服务器的线程接收到Message的消息后,也会创建一个Record存入Bucket(为防止多线程的访问冲突,用lock锁定对象),所以主页面上,绑定事件上,当触发事件的时候,ChatPeople字段就会改变,更新ChatPeople.QUid去Bucket里面寻找属于它的聊天记录,然后把这些聊天记录全部载入到ListView中去,而有个”清除缓存”的按钮,原理是也就是将Bucket关于该聊天对象的记录清除。
而搜索功能,考虑使用key参数对webapi进行请求,在服务器处理的时候,针对参数key,在TBUser表内筛选,而如果筛选成功后,会构成一个集合返回。这点同样运用在了搜索好友方面。
添加好友也是一个难点,在客户1添加客户2为好友后,如果客户2此刻在线的话,就会出现客户2没有客户1的好友这种情况,而如果客户1发送BaseModel消息给客户2,会略显麻烦,所以在设计的时候,在User/AddRelation接口上,服务器直接对该客户端发送请求,这样主动式的消息通知会更好
使用WebApi作为数据管理层,可以让客户端与数据库隔离开来,避免直接对数据库进行读写,起到保护数据库的作用。在客户端登录的时候,构造起登录URL进行POST请求,而登录返回值只有400和200,如果是200则登录成功,而当返回值为200的时候,服务器会返回登录用户的用户信息。
同理,在注册的时候,因为注册所需的是手机号,当请求发送至服务器的时候,服务器会利用外部API向用户手机发送一条注册信息,而对于服务器来说,会构造一个VerifyCodeModel的实例对该请求进行储存,该实例会包含一个验证码Code,手机号Phone和回调函数CallBack并且静态储存在UserController类中RegisterCodes字段。当用户点击确认按钮的时候,会对Verfiy函数进行请求,这个时候在RegisterCodes中找这个实例,如果找到并在规定时间上匹配成功了,就好调用CallBack函数将注册的信息写入数据库
找回密码同理,也是利用Verfiy请求进行的,不同于注册的时候,在重置密码的时候是对数据库进行修改的操作
首先对于服务器端,服务器应用会开启WebSocket服务,并且有个SocketController会接收登录。而对于该控制器的Connect接口为GET请求,含有参数uid,因为这里的WebSocket连接会在登录完成后进行连接,所以并没有对发来的请求中的uid进行验证,而当服务器握手成功后会开启监听线程转到Run方法,而Run方法本质上是对客户端上传信息进行阻塞监听,并根据信息进行转发.而本次WebSocket传输的是Json字符串,是由BaseModel序列化而来,所以服务器只需要查看BaseModel中的Sender和Receiever字段就可以对该则信息进行转发。
对于客户端,用户输入消息后会根据发送对象的UID形成一个BaseModel的实例对象,然后序列化后发送至服务器,由服务器通过连接的socket发送至目标,而目标在登录完成且与服务器握手成功后,会开启一个监听线程,如果收到了来自服务器的Json文本,会对其中的Sender字段进行判断,如果现在处于和发消息的人的聊天界面,就会将这个消息传入SendMessage函数中以此来显示在ListView控件中,如果不是处于该聊天界面,那么这个消息会被存入一个Bucket中缓存着,当用户打开与其的聊天对话,会自动将Bucekt中的消息载入到ListView中
对于聊天室,在载入主页面后,会在好友栏增加一个叫”幸福一家人”的Item,而该Item的UID为“HOST”,如果是来自”HOST”的信息,客户端的解析函数会将“HOST”解析到这个界面,而发送至服务的消息,服务器会对Sender和Receiever进行交换,解析的时候,就不再是[Sender] -> [Receiever]而直接是Receiever了,而Receiever就是发送信息的那个人
在好友栏的上方搜索框输入关键词后按下回车即可搜索,当点下回车后,客户端会对Friend/SearchFriend接口进行请求,key则为输入的关键词,而服务器会对关键词进行对比,会对比到EMail,Name,Phone,QUID等字段,如果有符合的就会返回,经客户端解析后会出现在左边好友栏,而在客户端进入主页面时和添加好友后都会重载一遍所有好友,所以需要重新查看好友的时候就直接空白输入然后回车即可
添加好友按钮在搜索框旁边,点击后会进入一个搜索界面,当输入关键词后,如同搜索好友一样,会对所以不是好友的进行搜索,并且返回值,可以对ListBox里面的数据进行选择,当选好后点击添加好友会发送请求至服务器并添加好友,关闭该窗口后会刷新好友列表。
而对于在线的人来说,如果没有刷新,则并没有对方,会产生单向好友的局面。为了解决这个问题,Friend/AddRelation接口会搜索所有连接的Clients,如果搜到了,则会构造一个BaseModel的实例,序列化后发送给该对象,而该客户端接收到这个Json字符串后,确定到该Action为Add则会对该字符串进行反序列化后,主动的在好友显示列表中加上该好友
删除好友是在好友栏右键打开上下文菜单,对选中的目标进行删除,在删除前,会用MessageBox进行询问,在确定后对Friend/DeleteRelation接口请求,在数据库层面删除对象,然后在UI层面上删除对象
修改名称和签名在签名或名称的地方双击即可进入一个TextBox,在TextBox中输入要修改的签名或者名称,然后按下ENTER键就可以提交,提交后后台对User/Modify接口进行请求,服务器进行对数据库的修改,然后完成修改,返回结果,如果修改成功,客户端UI线程就会更新显示
USE [DB_WeChat]
GO
/****** Object: Table [dbo].[TB_Account] Script Date: 2022/7/7 14:38:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TB_Account](
[Quid] [nchar](10) NOT NULL,
[Hash] [varbinary](16) NOT NULL,
CONSTRAINT [PK_TB_Account_1] PRIMARY KEY CLUSTERED
(
[Quid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[TB_ApiLog] Script Date: 2022/7/7 14:38:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TB_ApiLog](
[Index] [int] IDENTITY(1,1) NOT NULL,
[Region] [nchar](32) NOT NULL,
[Action] [nchar](32) NOT NULL,
[Arguments] [nvarchar](max) NULL,
[IP] [nchar](16) NULL,
[Address] [nchar](32) NULL,
[Guid] [nchar](11) NULL,
[LogTime] [datetime] NOT NULL,
CONSTRAINT [PK_TB_ApiLog] PRIMARY KEY CLUSTERED
(
[Index] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[TB_ChatLog] Script Date: 2022/7/7 14:38:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TB_ChatLog](
[Index] [int] IDENTITY(1,1) NOT NULL,
[Guid] [nchar](10) NOT NULL,
[Sender] [nchar](10) NOT NULL,
[Receiever] [nchar](10) NOT NULL,
[Action] [nchar](32) NOT NULL,
[Body] [nvarchar](max) NULL,
[SendTime] [datetime] NOT NULL,
CONSTRAINT [PK_TB_ChatLog] PRIMARY KEY CLUSTERED
(
[Guid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[TB_Relation] Script Date: 2022/7/7 14:38:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TB_Relation](
[Index] [int] IDENTITY(1,1) NOT NULL,
[RelationFrom] [nchar](10) NOT NULL,
[RelationTo] [nchar](10) NOT NULL,
CONSTRAINT [PK_TB_Relation] PRIMARY KEY CLUSTERED
(
[Index] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[TB_User] Script Date: 2022/7/7 14:38:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TB_User](
[Quid] [nchar](10) NOT NULL,
[Phone] [nchar](11) NOT NULL,
[Email] [nvarchar](max) NULL,
[Name] [nchar](16) NOT NULL,
[Sign] [nvarchar](max) NULL,
CONSTRAINT [PK_TB_User_1] PRIMARY KEY CLUSTERED
(
[Quid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
Run函数对每个连接的Socket进行监听,然后把消息转发给Forward函数
private async Task<bool> Run(string uid, WebSocket socket)
{
try
{
var db = Factory.Create<DB_WeChatContext>();
var buffer = new byte[16384]; //16kb缓存
WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
var data = Encoding.UTF8.GetString(buffer);
JObject pairs = JsonConvert.DeserializeObject<JObject>(data);
if (!pairs.ContainsKey("Verfiy")) break; //非软件连接
//写入消息Log
TbChatLog log = new()
{
Guid = (string)pairs["Guid"]??" ",
Action = (string)pairs["Action"]??" ",
Sender = (string)pairs["Sender"]??" ",
Receiever = (string)pairs["Receiever"]??" ",
Body = JsonConvert.SerializeObject(pairs["Body"]??" "),
SendTime = DateTime.Now
};
db.TbChatLog.Add(log);
db.SaveChanges();
await Forward(pairs, (string)pairs["Action"], socket);
buffer = new byte[16384];
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await socket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
Console.WriteLine($"|INFO|\t{uid}已断开连接");
return true;
}catch(Exception ex)
{
return false;
}
}
Forward函数会对接收到的data通过Sender和Reciever的关系进行转发
private async Task<bool> Forward(object data,string type,WebSocket socket)
{
switch(type)
{
case "Message":
{
var result = ((JObject)data).ToObject<BaseModel<MessageBody>>();
if(result.Receiever == "HOST")
{
Console.WriteLine($"|INFO|\t{result.Body.Message}");
var sender = result.Sender;
result.Sender = "HOST";
result.Receiever = sender;
//转发群消息
foreach(var item in Clients.Values.Except(new List<WebSocket>() { socket}))
{
await item.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result)), WebSocketMessageType.Text, true, CancellationToken.None);
}
return true;
}
//寻找Socket并转发消息
var client = Clients[Users[result.Receiever]];
await client.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result)), WebSocketMessageType.Text, true, CancellationToken.None);
Console.WriteLine($"|INFO|\t{result.Body.Message}");
}break;
}
return true;
}
在MainWindow的Load函数里面会载入Connect函数,Connect函数会连接到服务器的WebSocket然后开启一个监听线程接收来自服务器的消息
private async void Connect()
{
await ClientSocket.ConnectAsync(new Uri($"{StaticResource.WsUrl}?uid={Uid}"), CancellationToken.None);
_ = Task.Factory.StartNew(async () =>
{
while (true)
{
var buffer = new byte[1024 * 16];
var result = await ClientSocket.ReceiveAsync(buffer, CancellationToken.None);
CallBack(JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(buffer)));
buffer = new byte[1024 * 16];
}
});
}
CallBack函数会自动根据接收到的消息进行分类并显示,如果是Message消息,还会将该消息加入到Bucket里面,而使用Dispatcher是为了防止线程UI冲突
private void CallBack(JObject obj)
{
this.Dispatcher.Invoke(async () =>
{
if ((string)obj["Action"] == "Add")
{
var result = (await Friend.SearchFriend((string)obj["Sender"])).Data.FirstOrDefault();
if (result == null) return;
Peoples.Add(result);
Shows.Add(result);
LB_Friend.Items.Add(result.Name);
return;
}
if ((string)obj["Action"] == "Message")
{
Record record = new Record()
{
Sender = (string)obj["Sender"],
Receiever = (string)obj["Receiever"],
DateTime = DateTime.Now,
Message = (string)obj["Body"]["Message"]
};
if((string)obj["Sender"] == ChatPeople.Quid.TrimEnd())
{
ShowMessage(record);
}
//Message类加入Bucket
lock(Bucket)
{
Bucket.Put(record);
}
}
});
}
通过一条Record,分析Record的构成并显示在ListView控件上
private void ShowMessage(Record record)
{
if (record.Sender == "HOST")
{
string name = UNames.SearchName(record.Receiever, () => new UserHelper().GetUserNames(Uid).Result);
LV_Message.Items.Add($"[{DateTime.Now.ToString()}]\n{name}\n{record.Message}\n");
LV_Message.ScrollIntoView(LV_Message.Items[LV_Message.Items.Count - 1]);
return;
}
if (record.Sender == Uid)
{
string name = UNames.SearchName(record.Receiever, () => new UserHelper().GetUserNames(Uid).Result);
LV_Message.Items.Add($"[{DateTime.Now.ToString()}]\nMe -> {name}\n{record.Message}\n");
}
else
{
string name = UNames.SearchName(record.Sender, () => new UserHelper().GetUserNames(Uid).Result);
LV_Message.Items.Add($"[{DateTime.Now.ToString()}]\n{name} -> Me\n{record.Message}\n");
}
LV_Message.ScrollIntoView(LV_Message.Items[LV_Message.Items.Count - 1]);
}
<Window x:Class="WeChat.WPF.UI.LoginWindow"
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:WeChat.WPF.UI"
mc:Ignorable="d"
Title="登录" Height="350" Width="600">
<Grid Background="AliceBlue">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="500"/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Grid.ColumnSpan="3" Source="/UI/MainBackgroud.jpg" Stretch="Fill" Opacity="0.3"/>
<Grid Grid.Row="1" Grid.Column="1" Grid.RowSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="100"/>
Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="账号" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="1" Grid.Column="0" Content="密码" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="TB_Uid" Grid.Row="0" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<PasswordBox x:Name="TB_Password" Grid.Row="1" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<Button x:Name="BT_Login" Grid.Row="2" Grid.Column="1" Margin="10,15,10,15" Click="BT_Login_Click" Content="登录" FontSize="20" Cursor="Hand">
<Button.Background>
<LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
<GradientStop Color="#FF64BAC5" Offset="0"/>
<GradientStop Color="#FFBBFFFA" Offset="0.645"/>
LinearGradientBrush>
Button.Background>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderThickness="1" CornerRadius="30" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
Border>
ControlTemplate>
Button.Template>
Button>
<Label x:Name="Lab_Regsiter" Grid.Row="0" Grid.Column="2" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center">
<Hyperlink Click="Lab_Regsiter_Click">注册账号Hyperlink>
Label>
<Label x:Name="Lab_Reset" Grid.Row="1" Grid.Column="2" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center">
<Hyperlink Click="Lab_Reset_Click">找回密码Hyperlink>
Label>
Grid>
Grid>
Window>
<Window x:Class="WeChat.WPF.UI.RegisterWindow"
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:WeChat.WPF.UI"
mc:Ignorable="d"
Title="注册账号" Height="350" Width="600">
<Grid Background="AliceBlue">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="500"/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Image Grid.Column="0" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="3" Source="/UI/LoginBackgroud.png" Stretch="Fill" Opacity="0.3"/>
<Grid Grid.Row="1" Grid.Column="1" Grid.RowSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="100"/>
Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="手机号" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="1" Grid.Column="0" Content="密码" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="2" Grid.Column="0" Content="名称" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="3" Grid.Column="0" Content="验证码" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="TB_Uid" Grid.Row="0" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<TextBox x:Name="TB_Name" Grid.Row="2" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<TextBox x:Name="TB_VerfiyCode" Grid.Row="3" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<Label x:Name="Lab_SendCode" Grid.Row="3" Grid.Column="2" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center">
<Hyperlink Click="Lab_SendCode_Click">发送验证码Hyperlink>
Label>
<PasswordBox x:Name="TB_Password" Grid.Row="1" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<Button x:Name="BT_Login" Grid.Row="4" Grid.Column="1" Margin="15,5,15,5" Content="注册" Click="BT_Login_Click">
<Button.Background>
<LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
<GradientStop Color="#FF64BAC5" Offset="0"/>
<GradientStop Color="#FFBBFFFA" Offset="0.645"/>
LinearGradientBrush>
Button.Background>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderThickness="1" CornerRadius="30" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
Border>
ControlTemplate>
Button.Template>
Button>
Grid>
Grid>
Window>
<Window x:Class="WeChat.WPF.UI.ResetWindow"
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:WeChat.WPF.UI"
mc:Ignorable="d"
Title="重置密码" Height="350" Width="600">
<Grid Background="AliceBlue">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="500"/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Grid.ColumnSpan="3" Source="/UI/Reset.png" Stretch="Fill" Opacity="0.7"/>
<Grid Grid.Row="1" Grid.Column="1" Grid.RowSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="100"/>
Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="手机号" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="1" Grid.Column="0" Content="新密码" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="2" Grid.Column="0" Content="验证码" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="TB_Uid" Grid.Row="0" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<TextBox x:Name="TB_VerfiyCode" Grid.Row="2" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<Label x:Name="Lab_SendCode" Grid.Row="2" Grid.Column="2" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center">
<Hyperlink Click="Lab_SendCode_Click">发送验证码Hyperlink>
Label>
<PasswordBox x:Name="TB_Password" Grid.Row="1" Grid.Column="1" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20"/>
<Button x:Name="BT_Login" Grid.Row="3" Grid.Column="1" Margin="15,5,15,5" Content="重置" Click="BT_Login_Click" FontSize="20" >
<Button.Background>
<LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
<GradientStop Color="#FFC4C564" Offset="0"/>
<GradientStop Color="#FFF2FFBB" Offset="0.645"/>
LinearGradientBrush>
Button.Background>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderThickness="1" CornerRadius="30" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
Border>
ControlTemplate>
Button.Template>
Button>
Grid>
Grid>
Window>
<Window x:Class="WeChat.WPF.UI.RelationWindow"
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:WeChat.WPF.UI"
mc:Ignorable="d"
Title="添加好友" Height="250" Width="400">
<Grid Background="Azure">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
Grid.RowDefinitions>
<Label Content="搜索" FontSize="15" Grid.Column="0" Grid.Row="0" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<TextBox x:Name="TB_Key" Grid.Row="0" Grid.Column="1" Margin="20,15,20,15"/>
<Image Grid.Column="2" Grid.Row="0" Source="/UI/Search.png" Margin="15,15,15,15" MouseLeftButtonDown="Image_MouseLeftButtonDown" Cursor="Hand"/>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130"/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
Grid.RowDefinitions>
<ListBox x:Name="LB_User" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" SelectionChanged="LB_User_SelectionChanged"/>
<TextBlock x:Name="TB_UserMessage" Grid.Row="0" Grid.Column="1" Margin="20,20,20,15"/>
<Button x:Name="BT_Add" Grid.Row="1" Grid.Column="1" Content="添加好友" Margin="160,20,40,10" Click="TB_Add_Click"/>
Grid>
Grid>
Window>
<Window x:Class="WeChat.WPF.UI.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:WeChat.WPF.UI"
mc:Ignorable="d"
Title="WeChat" Height="675" Width="1200" Loaded="Window_LoadedAsync" Closing="Window_Closing">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Image Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Source="/UI/Picture.png" Stretch="Fill" Margin="10,10,10,10"/>
<Label Grid.Row="0" Grid.Column="1" x:Name="Lab_UserName" Content="纸墨青鸢" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="LightPink" MouseDoubleClick="Lab_UserName_MouseDoubleClick"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="Lab_H_UserName" FontSize="20" Visibility="Hidden" TextBlock.TextAlignment="Left" HorizontalAlignment="Center" TextWrapping="Wrap" KeyDown="Lab_H_UserName_KeyDown" VerticalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" x:Name="TB_Sign" Text="疏影横斜水清浅,暗香浮动月黄昏。" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap" MouseLeftButtonDown="TB_Sign_MouseLeftButtonDown" />
<TextBox Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" x:Name="TB_H_Sign" Visibility="Hidden" KeyDown="TB_H_Sign_KeyDown" TextBlock.TextAlignment="Left" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" />
Grid>
<Grid Grid.Row="1" Grid.RowSpan="4" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" x:Name="TB_Search" TextBlock.TextAlignment="Left" VerticalAlignment="Center" FontSize="20" KeyDown="TB_Search_KeyDown"/>
<Image Grid.Row="0" Grid.Column="1" x:Name="PB_AddFriend" Source="/UI/Add.png" Cursor="Hand" MouseLeftButtonDown="PB_AddFriend_MouseLeftButtonDown"/>
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="LB_Friend" d:ItemsSource="{d:SampleData ItemCount=5}" SelectionChanged="LB_Friend_SelectionChanged">
<ListBox.ContextMenu>
<ContextMenu x:Name="CTM_Friend">
<MenuItem Header="删除" Click="MenuItem_Click"/>
ContextMenu>
ListBox.ContextMenu>
ListBox>
Grid>
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.Column="2" Grid.ColumnSpan="6">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Grid Grid.Column="0" Grid.Row="0">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
<GradientStop Color="#FFC2F043" Offset="0"/>
LinearGradientBrush>
Grid.Background>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" x:Name="Lab_ChatName" VerticalAlignment="Center" Content="幸福一家人" FontSize="20" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="0" x:Name="TB_ChatSign" Text="疏影横斜水清浅,暗香浮动月黄昏。" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap"/>
Grid>
<Grid Grid.Row="1" Grid.RowSpan="5" Grid.Column="0" Background="#f4f6e8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
Grid.RowDefinitions>
<ListView Grid.Row="0" Grid.Column="0" x:Name="LV_Message" FontSize="15" TextBlock.TextAlignment="Left" BorderBrush="#f4f6e8">
<ListView.Background>
<ImageBrush ImageSource="/UI/ChatGroud.png" Stretch="Fill"/>
ListView.Background>
ListView>
Grid>
<Grid Grid.Row="6" Grid.Column="0" Grid.RowSpan="2" Background="Azure">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" x:Name="TB_Message" TextWrapping="Wrap" KeyDown="TB_Message_KeyDown" FontSize="15">
<TextBox.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFB4FFF4" Offset="1"/>
LinearGradientBrush>
TextBox.Background>
TextBox>
<Button Grid.Column="1" Grid.Row="0" x:Name="BT_ClearText" Content="清除文本" Click="BT_ClearText_Click" Margin="10,10,10,10"/>
<Button Grid.Column="1" Grid.Row="1" x:Name="BT_Clear" Content="清除缓存" Click="BT_Clear_Click" Margin="10,10,10,10"/>
<Button Grid.Column="1" Grid.Row="2" x:Name="BT_Send" Content="发送" Click="BT_Send_Click" Margin="10,10,10,10"/>
Grid>
Grid>
Grid>
Window>
①点击进入CSDN文件下载
②加入爱好者QQ群:1065703520
③联系作者QQ:1215971512
技术仅供学习,使用到的图片均来自网上,如侵权,请联系作者删除