• 系统功能基本要求
  • wpf局域网通信
  • WPF跨线程访问线程安全的数据如解决该类型的CollectionView不支持从调度程序线程以外的线程对其SourceCollection
  • 读取信息null
  • 读取发来数据需要时间
  • 连接数据库
  • 数据库获得数据
  • 发送数据



系统功能基本要求

  • 通讯录信息
    -通讯人姓名
    -联系方式
    -工作地点
    -城市
    -备注
  • 备忘录信息
    -包括什么时间
    -事件
    -地点
  • 日记信息
    -时间
    -地点
    -事情
    -人物
    -个人财物管理
    -总收入
    -消费项目
    -消费金额
    -消费时间
    -剩余资金

wpf局域网通信

wpf局域网聊天

想到做一个c/s,传输我就想到了json

json需要先安装nuget中Newtonsoft.Json;

wpf C# 数据库 c/s 个人信息管理 wpf局域网通信_sql

安装

因为要找比较多,可以搜索

wpf C# 数据库 c/s 个人信息管理 wpf局域网通信_json_02

然后在文件写​​using Newtonsoft.Json;​

把类转json

JsonConvert.SerializeObject(obj)

把json转类

private T Deserialize<T>(string str)
{
try
{
T temp = JsonConvert.DeserializeObject<T>(str);
return temp;
}
catch (JsonException e)
{
reminder = "输入不是ObservableCollection<T> json" + e.Message;
}
return default(T);
}

参考很多博客写出了局域网传输的代码

服务器:

public class principal_Computer
{
public principal_Computer(System.Action<string> ReceiveAction)
{
this.ReceiveAction = ReceiveAction;

ServerSocket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;
ServerInfo = new IPEndPoint(ip , port);
ServerSocket.Bind(ServerInfo);//将SOCKET接口和IP端口绑定
ServerSocket.Listen(10);//开始监听,并且挂起数为10

ClientSocket = new Socket[65535];//为客户端提供连接个数
ClientNumb = 0;//数量从0开始统计

ServerThread = new Thread(new ThreadStart(RecieveAccept));//将接受客户端连接的方法委托给线程
ServerThread.Start();//线程开始运行
}

public void send(string str)
{
byte[] MsgBuffer = encoding.GetBytes(str);
for (int i = 0; i < ClientNumb; i++)
{
if (ClientSocket[i].Connected)
{
//回发数据到客户端
ClientSocket[i].Send(MsgBuffer , 0 , MsgBuffer.Length , SocketFlags.None);
}
}
}

//接受客户端连接的方法
private void RecieveAccept()
{
while (true)
{
//Accept 以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket。
//在阻止模式中,Accept 将一直处于阻止状态,直到传入的连接尝试排入队列。连接被接受后,原来的 Socket 继续将传入的连接请求排入队列,直到您关闭它。
byte[] buffer = new byte[65535];
ClientSocket[ClientNumb] = ServerSocket.Accept();
ClientSocket[ClientNumb].BeginReceive(buffer , 0 , buffer.Length , SocketFlags.None ,
new AsyncCallback(RecieveCallBack) , ClientSocket[ClientNumb]);
ReceiveAction(encoding.GetString(buffer));
ClientNumb++;
}
}

//回发数据给客户端
private void RecieveCallBack(IAsyncResult AR)
{
try
{
Socket RSocket = (Socket)AR.AsyncState;
byte[] MsgBuffer = new byte[65535];
//同时接收客户端回发的数据,用于回发
RSocket.BeginReceive(MsgBuffer , 0 , MsgBuffer.Length , SocketFlags.None , ar =>
{
try
{
RSocket.EndReceive(ar);
ReceiveAction(encoding.GetString(MsgBuffer).Trim('\0' , ' '));
RecieveCallBack(ar);
}
catch (SocketException e)
{
ReceiveAction("对方断开连接" + e.Message);
}

} , RSocket);

}
catch
{
}

}

private IPEndPoint ServerInfo;//存放服务器的IP和端口信息
private Socket ServerSocket;//服务端运行的SOCKET
private Thread ServerThread;//服务端运行的线程
private Socket[] ClientSocket;//为客户端建立的SOCKET连接
private int ClientNumb;//存放客户端数量
//private byte[] MsgBuffer;//存放消息数据
private int port = 54321; //端口号
private System.Action<string> ReceiveAction;
private Encoding encoding = Encoding.Default;

}

客户端

public class slaveComputer
{
public slaveComputer(System.Action<string> ReceiveAction)
{
this.ReceiveAction = ReceiveAction;
//定义一个IPV4,TCP模式的Socket
ClientSocket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
}
public void access(string ip)
{
ServerInfo = new IPEndPoint(IPAddress.Parse(ip) , port);
//客户端连接服务端指定IP端口,Sockket
ClientSocket.Connect(ServerInfo);
//将用户登录信息发送至服务器,由此可以让其他客户端获知
ClientSocket.Send(encoding.GetBytes(" 进入系统!\n"));
//开始从连接的Socket异步读取数据。接收来自服务器,其他客户端转发来的信息
//AsyncCallback引用在异步操作完成时调用的回调方法
ReceiveCallBack(ReceiveAction);
}

public void send(string str)
{
byte[] buffer = encoding.GetBytes(str);
ClientSocket.Send(buffer , 0 , buffer.Length , SocketFlags.None);
}
private IPEndPoint ServerInfo;
private Socket ClientSocket;
private Encoding encoding = Encoding.Default;
private System.Action<string> ReceiveAction;
private void ReceiveCallBack(System.Action<string> ReceiveAction)
{
try
{
//结束挂起的异步读取,返回接收到的字节数。 AR,它存储此异步操作的状态信息以及所有用户定义数据
byte[] MsgBuffer = new byte[65535];
ClientSocket.BeginReceive(MsgBuffer , 0 , MsgBuffer.Length , SocketFlags.None , ar =>
{
//对方断开连接时, 这里抛出Socket Exception
//An existing connection was forcibly closed by the remote host
try
{

ClientSocket.EndReceive(ar);
ReceiveAction(encoding.GetString(MsgBuffer).Trim('\0' , ' '));
ReceiveCallBack(ReceiveAction);
}
catch (SocketException e)
{
ReceiveAction("对方断开连接" + e.Message);
}

} /*new AsyncCallback(ReceiveCallBack)*/ , null);

}
catch
{

}
}
private int port = 54321; //端口号

}

使用方法是

写出一个​​private System.Action<string> ReceiveAction;

初始化

ReceiveAction = str =>
{
//因为接受使用byte[] 会有很多空的字符,去掉就是消息
string temp= str.Trim('\0' , ' ');
if (!string.IsNullOrEmpty(temp))
{
//reminder是提示的字符串
reminder = temp;
}
};

使用服务器

var _principal_Computer = new principal_Computer(ReceiveAction);

使用客户端

var _slaveComputer = new slaveComputer(ReceiveAction);            
_slaveComputer.access(ip);

客户端和服务器发送都是使用send

而服务器有多个客户端,发送可以指明发送到哪个客户端,可以全发送。

发送信息时,对信息进行一个类转json

因为发送信息不知道需要做什么,于是做了一个枚举,一个类

/// <summary>
/// 发送消息
/// </summary>
public class ctransmitter
{
public ctransmitter(int id , ecommand command , string str/*,int ran*/)
{
this.id = id.ToString();
this.command = command.ToString();
this.str = str;
//this.ran = ran.ToString();
}

public string id
{
set;
get;
}
public string command
{
set;
get;
}
public string str
{
set;
get;
}
//public string ran
//{
// set;
// get;
//}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}

public enum ecommand
{
login,//登录
id,//分配id
faddressBook,//通讯录
ce
}

ctransmitter里面ToString把这个类转换为json,之后接收就把json转换为类,然后根据command来得到发送的命令

ctransmitter transmitter = JsonConvert.DeserializeObject<ctransmitter>(json);
ecommand command = (ecommand)Enum.Parse(typeof(ecommand) , transmitter.command);
switch (command)
{
case ecommand.ce:
reminder("收到" + transmitter.id);
break;
}

客户端循环等待服务器发送的消息,收到就把信息放到一个byte[]如果byte大小不够,分为多次。

服务器把信息发送可以依靠客户端所在的id或全部发。

所有数据转换为Default

​private Encoding encoding = Encoding.Default;​

ClientSocket[i]存放不如使用list来存放,因为这个长度固定,在很多机器连接断开就没了,不过这个是简单的,所以用了ClientSocket[i]

端口号都被写成没法修改,这个可以放在view一个textbox来修改

生成addressBook界面

先做一个ViewModel

viewaddressBook.cs

因为需要访问​​_model​​​,在构造传入viewModel初始的​​_model​

public viewaddressBook(model.model _model)
{
this._model = _model;
}

WPF跨线程访问线程安全的数据(如解决:该类型的CollectionView不支持从调度程序线程以外的线程对其SourceCollection)

更新数据中途出现了该类型的CollectionView不支持从调度程序线程以外的线程对其SourceCollection

查到了使用invoke就可以

从输入一个string然后更新

private void newaddressBook(string str)
{
try
{
ObservableCollection<caddressBook> temp = DeserializeObject<caddressBook>(str);

System.Windows.Application.Current.Dispatcher.Invoke
(() =>
{
addressbook.Clear();

foreach (var t in temp)
{
addressbook.Add(t);
}
});
}

catch (JsonException e)
{
reminder = "输入不是ObservableCollection<caddressBook> json" + e.Message;
}
}

读取信息null

在运行返回一个错误

null

查询了是读取有一个null

const string id = "id";
const string MTIME = "MTIME";
const string PLACE = "PLACE";
const string INCIDENT = "INCIDENT";
const string CONTACTSID = "NAME";
using (SqlConnection sql = new SqlConnection(connect))
{
sql.Open();
using (SqlCommand cmd = new SqlCommand(strsql , sql))
{
using (SqlDataReader read = cmd.ExecuteReader())
{
if (!read.HasRows)
return diary;
int idindex = read.GetOrdinal(id);
int MTIMEindex = read.GetOrdinal(MTIME);
int PLACEindex = read.GetOrdinal(PLACE);
int INCIDENTindex = read.GetOrdinal(INCIDENT);
int CONTACTSIDindex = read.GetOrdinal(CONTACTSID);
while (read.Read())
{
diary.Add(new cdiary()
{
id = read.GetInt32(idindex).ToString() ,
MTIME = read.GetDateTime(MTIMEindex).ToString() ,
PLACE = read.GetString(PLACEindex) ,
incident = read.GetString(INCIDENTindex) ,
CONTACTSID = read.GetString(CONTACTSIDindex)
});
}
}
}
}

里面有些是null

使用

PLACE = read[PLACEindex] as string;

就可以

出现了把PLACE.Trim();错误可以使用

private string DBNullstring<T>(object obj)
{
try
{
return obj == System.DBNull.Value ? " " : ( (T)obj ).ToString();
}
catch
{
return string.Empty;
}
}

读取发来数据需要时间

byte[] MsgBuffer = new byte[65535];
ClientSocket.BeginReceive(MsgBuffer , 0 , MsgBuffer.Length , SocketFlags.None , ar =>
{
//对方断开连接时, 这里抛出Socket Exception
//An existing connection was forcibly closed by the remote host
try
{
ClientSocket.EndReceive(ar);
ReceiveAction(encoding.GetString(MsgBuffer).Trim('\0' , ' '));
ReceiveCallBack(ReceiveAction);
}
catch (SocketException e)
{
reminder("对方断开连接" + e.Message);
}
catch(ObjectDisposedException e)
{
reminder (id.ToString() + "连接退出" + e.Message);
return;
}

MsgBuffer比发来信息小,那么分为多次,而在识别json会出现把字符串弄成多个,这样就出现json错误

需要在发送使用分开或使用大的buffer

连接数据库

连接数据库

$"Data Source={DataSource};Initial Catalog={InitialCatalog};Integrated Security=True";
DataSource 数据库ip
InitialCatalog 数据库名

strsql是sql语句

using (SqlConnection sql = new SqlConnection(connect))
{
sql.Open();
using (SqlCommand cmd = new SqlCommand(strsql , sql))
{
using (SqlDataReader read = cmd.ExecuteReader())
{
try
{
if (!read.HasRows)
return null;
//读数据
}
catch
{

}
}
}
}

数据库获得数据

从数据库获得数据可以使用​​SqlDataReader read = cmd.ExecuteReader()​

读取read的内容

连接

const string addressBookname = "vaddressbook";
string sqlAddressBook = $"{usesql}{line}SELECT * FROM {addressBookname};";

使用read[str]可以获得所在数据,

const string id = "id";
const string name = "name";
const string contact = "contact";
const string caddress = "caddress";
const string city = "city";
const string comment = "comment";

获得字符串所在

int idindex = read.GetOrdinal(id);
int nameindex = read.GetOrdinal(name);
int contactindex = read.GetOrdinal(contact);
int caddressindex = read.GetOrdinal(caddress);
int cityindex = read.GetOrdinal(city);
int commentindex = read.GetOrdinal(comment);
caddressBook temp = new caddressBook
{
id = read.GetInt32(idindex).ToString() ,
name = read.GetString(nameindex).Trim() ,
contact = read.GetString(contactindex).Trim() ,
address = read.GetString(caddressindex).Trim() ,
city = read.GetString(cityindex).Trim() ,
comment = read.GetString(commentindex).Trim()
};

这种 方法使用比较难,因为需要写很多字符串和int。

对string可以直接​​str=read[str] as string;​

发送数据

public void send(string str)
{
if (ClientSocket.Connected)
{
byte[] buffer = encoding.GetBytes(str);
ClientSocket.Send(buffer , 0 , buffer.Length , SocketFlags.None);
}
else
{
try
{
access(ip);
}
catch(SocketException e)
{
reminder ("连接失败 " + e.Message);
}
catch(InvalidOperationException e)
{
reminder("服务器没有开启"+e.Message);
}
}
}

客户端可以设置ip

<TextBlock Text="ip" />
<TextBox Text="{Binding Path=_model.ip,Mode=TwoWay}" Width="100"/>
<Button Content="确定" Click="确定"/>

添加通讯

public void add()
{
if (accord())
{
foreach (var t in laddressBook)
{
if (addressbook_equals(addressBook, t))
{
warn = "信息重复";
return;
}
}
_model.send(ecommand.addaddressBook , addressBook.ToString());
reminder = "添加通讯";
addressBook = new caddressBook();
}
}

查询可以没有输入为*

删除,判断有没修改,如果没有发送删除的类

if (addressBook.Equals(_item))
{
_model.send(ecommand.daddressBook , _item.ToString());
reminder = "删除通讯";
}

通讯录 日记都一样,只是改了名字和一些,日记需要时间,所以判断是否存在时间,如果有,格式是否正确。

程序难在写了好多次不同的数据,这些可以合并为一个,然后修改,看到通讯录重复,没有去把重复的作为一个类,每个都写一次,如果要修改,经常出错,git可以撤销

git reset --hard id