WPF项目学习.四
信息收录项目
版权声明:本文为博主初学经验,未经博主允许不得转载。
一、前言
记录在学习与制作WPF过程中遇到的解决方案。
需求文案、设计思路、简要数据库结构、简要流程图和明细代码,动图细化每步操作,入门级引导文章;
项目功能包括:登录、首页、数据维护 和 全文搜索等增删查改的常用操作;
二、配置
系统环境:
win10
开发工具:
Visual Studio 2017
开发语言:
C#.WPF (MVVM框架)
数据库:
SQLiteStudio
三、附件
- vs_enterprise.exe 在线安装 Visual Studio 2017 开发工具;
- SQLiteStudio.zip 免安装Sqlite轻量数据库操作工具;
- SearchData.zip 项目源代码;
四、项目需求
1. 项目背景:
前一段时间,一个老同事找到我说,有个项目一起搞,他负责营销,我负责平台;然后我就咨询了他关于项目的理念,听完通过电话聊天的述讲后,我只能呵呵一句,因为他还是着眼于眼前的需求,为了不打击他的激情,我不评论项目发展前景;完全配合他的计划,并加以我的思想完善整个项目趋势;接着就约了个时间详谈;通过茶水间的讨论后,才知道他只是要一个专属的OA平台而已,需求也甚是简单;因此我直接用该需求来作为项目制作的全过程指导文章;
2. 项目概述:
文字描述:
做一个信息搜索平台,平台主要角色是需求方信息,用户信息和解决需求的信息;
举例描述:我拿到一个厂家销售共享冰箱的项目,通过平台快速搜索到相关代理商和有共享方面销售的企业;
或者厂家直接通过平台搜索有能力解决他商品销售的企业,然后通过平台联系,平台赚取中介费;
流程图描述:
3. 需求目标:
把需求的数据有效统一整理,方便查阅,快速处理需求方和解决方的对接;
持久化有质量且高效的对接案例,从而能够为后续发展做铺垫;
4. 用户介绍
注册用户,游客,管理员;
用户类型包括厂家,小作坊,企业,商家,个人,代理商等等各种行业;
5. 功能
登录功能,搜索功能,数据维护的新增和编辑,数据的导出和导入,注册用户不能查阅其他用户维护的数据;
6. 项目制作时间规划
.(个人单独开发).
7. 风险评估
由于经济条件,目前先制作离线本地版本,后期改进后再数据统一管理;
五、设计思路
使用MVVM的其中一个优点在于数据与视图的设计分离;
因此可以省略画草图,直接进行视图设计;
1. 登录页面
登录输入包括账号和密码,密码用“*”号隐藏字符;输入框带有水印提示;
2. 注册页面
注册输入包括账号,密码,手机号,邮箱,QQ等信息进行收录。同时,登录的时候也可以用账号、手机号、邮箱和QQ;
由于注册页面的输入框做了一个自定义控件,所以在前端设计时并没有赋值,待完善后台代码,项目运行启动时自动赋值;
3. 数据维护页面
维护数据包括公司名称,联系姓名,电话,邮箱,QQ,法人代表,法人联系方式,企业固话,传真,营业执照,税号,注册资本,等级资质,地址,销售物品相关,企业标签,公司简介,备注;
百度了一下公司的组成信息就写了这些,备注算是补充字段了,后期再搞一个版本是自定义维护字段,这一期先不弄;
4. 导出页面
根据时间,标签和销售相关进行数据导出;
5. 搜索页面
模糊搜索,可以搜索所有字段信息;
可以自定义列的显示,左边是搜索结果,右边是点击左边搜索结果的条目后,阅览更详细的信息;
但更详细的信息中却隐藏了关键信息,拥有相关权限的用户点击联系客服后会显示关键信息,其他普通权限的用户只会以邮件的详细发给设置好的客服索取关键信息;
6. 设置页面
关键信息设置隐藏,基础邮箱配置;
7. 样式使用的操作动图
六、数据库结构
1. 引用:
Chloe.dll ; Chloe.SQLite ; System.Data.SQLite
2. SQLite的数据库常规操作的通用类
public class SqLiteSqlHelper { private readonly string _connectionString; public SqLiteSqlHelper() { _connectionString = DbConfiger.SqLiteConnectionString; } /// <summary> /// 构造函数 /// </summary> /// <param name="dbPath">SQLite数据库文件路径</param> public SqLiteSqlHelper(string dbPath) { _connectionString = "Data Source=" + dbPath; } /// <summary> /// 对SQLite数据库执行增删改操作,返回受影响的行数。 /// </summary> /// <param name="sql">要执行的增删改的SQL语句</param> /// parameters 执行增删改语句所需要的参数,参数必须以它们在SQL语句中的顺序为准 public int ExecuteNonQuery(string sql, SQLiteParameter[] parameters) { int affectedRows; using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (DbTransaction transaction = connection.BeginTransaction()) { using (var command = new SQLiteCommand(connection)) { command.CommandText = sql; if (parameters != null) command.Parameters.AddRange(parameters); affectedRows = command.ExecuteNonQuery(); } transaction.Commit(); } } return affectedRows; } /// <summary> /// 执行一个查询语句,返回一个关联的SQLiteDataReader实例 /// </summary> /// <param name="sql">要执行的查询语句</param> /// parameters 执行SQL查询语句所需要的参数,参数必须以它们在SQL语句中的顺序为准 public SQLiteDataReader ExecuteReader(string sql,SQLiteParameter[] parameters) { var connection = new SQLiteConnection(_connectionString); var command = new SQLiteCommand(sql, connection); if (parameters != null) { command.Parameters.AddRange(parameters); } connection.Open(); return command.ExecuteReader(CommandBehavior.CloseConnection); } /// <summary> /// 执行一个查询语句,返回一个包含查询结果的DataTable /// </summary> public DataTable ExecuteDataTable(string sql, SQLiteParameter[] parameters) { using (var connection = new SQLiteConnection(_connectionString)) { using (var command = new SQLiteCommand(sql, connection)) { if (parameters != null) command.Parameters.AddRange(parameters); var adapter = new SQLiteDataAdapter(command); var data = new DataTable(); adapter.Fill(data); return data; } } } /// <summary> /// 执行一个查询语句,返回查询结果的第一行第一列 /// </summary> public object ExecuteScalar(string sql, SQLiteParameter[] parameters) { object obj; using (var connection = new SQLiteConnection(_connectionString)) { using (var command = new SQLiteCommand(sql, connection)) { if (parameters != null) command.Parameters.AddRange(parameters); connection.Open(); obj = command.ExecuteScalar(); connection.Close(); } } return obj; } /// <summary> /// 查询数据库中的所有数据类型信息 /// </summary> public DataTable GetSchema() { using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); var data = connection.GetSchema("TABLES"); connection.Close(); return data; } } }
判断表是否存在
public static bool IsExistsTable(string tableName) { var sql = string.Format(@"
SELECT COUNT(*)
FROM sqlite_master
where type='table' and name='{0}';",
tableName); var count = new SqLiteSqlHelper().ExecuteScalar(sql, null); return Convert.ToInt16(count) > 0; }
数据库数据用实体类操作
/// <summary> /// SQLite数据访问 /// </summary> private static readonly SqLiteDbProvider DbProvider = new SqLiteDbProvider(); /// <summary> /// 获取用户数据 /// </summary> public static List<Users> GetUsers(string id) { var result = DbProvider.DbContext.Query<Users>() .Where(t => t.Id == id).ToList(); if (result != null) return result; result = DbProvider.DbContext.Query<Users>() .Where(t => t.UserCode == id || t.Phone == id || t.Email == id || t.Qq == id).ToList(); return result; }
/// 更新用户数据
public static void Delete(Contents content)
{
if (content == null) return;
DbProvider.DbContext.Update<Contents>(t => t.Id == content.Id, t => content);
}
3. 数据库表
3.1 用户信息表
3.2 企业信息表
3.3 系统配置表
4. 库表关系
5.配套实体类写在Model层
这里就不贴代码了,具体看源代码;
七、通用代码集
【GuidHelper】
/// 获取有序的唯一ID。 public static Guid GenerateComb(
SequentialGuidType sequentialGuidType = SequentialGuidType.SequentialAsString) { return SequentialGuidGenerator.NewSequentialGuid(sequentialGuidType); } // 根据枚举生成不同的有序GUID private static class SequentialGuidGenerator { private static readonly RNGCryptoServiceProvider Rng = new RNGCryptoServiceProvider(); public static Guid NewSequentialGuid(SequentialGuidType guidType) { var randomBytes = new byte[10]; Rng.GetBytes(randomBytes); var timestamp = DateTime.UtcNow.Ticks / 10000L; var timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian) { Array.Reverse(timestampBytes); } var guidBytes = new byte[16]; switch (guidType) { case SequentialGuidType.SequentialAsString: case SequentialGuidType.SequentialAsBinary: Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6); Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10); if (guidType == SequentialGuidType.SequentialAsString
&& BitConverter.IsLittleEndian) { Array.Reverse(guidBytes, 0, 4); Array.Reverse(guidBytes, 4, 2); } break; case SequentialGuidType.SequentialAtEnd: Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10); Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6); break; } return new Guid(guidBytes); } } /// <summary> /// 有序GUID的类型 /// sqlServer用AtEnd; /// mysql用AsString或者AsBinary; /// oracle用AsBinary; /// postgresql用AsString或者AsBinary; /// </summary> public enum SequentialGuidType { SequentialAsString, SequentialAsBinary, SequentialAtEnd }
【JsonHelper】
/// <summary> /// 转换成JSON格式 /// </summary> /// <typeparam name="T">转换类型</typeparam> /// <param name="obj">string内容</param> public static T ToJson<T>(this string obj) where T : class, new() { var model = new T(); var serializer = new DataContractJsonSerializer(model.GetType()); var mStream = new MemoryStream(Encoding.UTF8.GetBytes(obj)); return serializer.ReadObject(mStream) as T; } /// <summary> /// JSON转换String /// </summary> /// <typeparam name="T">转换类型</typeparam> /// <param name="obj">Json内容</param> public static string JsonToString<T>(this T obj) { var serializer = new DataContractJsonSerializer(obj.GetType()); var mStream = new MemoryStream(); serializer.WriteObject(mStream, obj); byte[] dataBytes = new byte[mStream.Length]; mStream.Position = 0; mStream.Read(dataBytes, 0, (int) mStream.Length); return Encoding.UTF8.GetString(dataBytes); }
【LoggerHelper】
/// <summary> /// 插入日志内容 /// </summary> /// <param name="msg">内容</param> public static void Insert(string msg) { Insert(msg, "Logger"); } /// <summary> /// 插入日志内容 /// </summary> /// <param name="msg">内容</param> /// <param name="logName">日志名称</param> public static void Insert(string msg, string logName) { var name=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,logName+".dat"); using (var sw = new StreamWriter(name, true, Encoding.UTF8)) { sw.WriteLineAsync($"{DateTime.Now} {msg}"); } }
【TryCatch】
/// <summary> /// 切换回主UI线程 并加Try Catch处理事件 /// </summary> /// <param name="action">try里面的执行方法</param> /// <param name="param">try里面的方法参数</param> /// <param name="msg">系统异常</param> /// <param name="workMsg">正常业务异常</param> /// <param name="end">finally执行的事件</param> public static void TryInvoke<T>(Action<T> action, T param,
string msg = null, string workMsg = null, Action end = null) { try { Application.Current.Dispatcher.BeginInvoke(new Action(() => { action(param); })); } catch (Common.BaseModels.WorkException e) { var errWord = workMsg ?? "业务异常"; var err = $"{errWord}:{e.Message}"; MessageBoxManager.ShowWorkError(err); } catch (Exception ex) { var errWord = msg ?? "系统异常"; var err = $"{errWord}:{ex.Message}"; Common.LoggerHelper.Insert(ex); MessageBoxManager.ShowError(err); } finally { end?.Invoke(); } }
【TabControlManager】(标签管理)
/// <summary> /// 添加标签页子项 /// </summary> public static void AddTabItem(TabControl tabControl, TabItem tabItem) { if (!IsContains(tabControl, tabItem, out var selectedIndex)) tabControl.Items.Add(tabItem); tabControl.SelectedIndex = selectedIndex; } /// <summary> /// 检查tabItem是否已经包含在了tabControl中 /// </summary> public static bool IsContains(
TabControl tabControl, TabItem tabItem, out int selectedIndex) { selectedIndex = 0; foreach (TabItem item in tabControl.Items) { if (item.Name == tabItem.Name) return true; selectedIndex++; } return false; }
【ConvertHelper】
/// <summary> /// 将原数据转为目标数据 /// 注:支持批量,但只支持列表接口子类,如:List /// </summary> public static TTo ConvertToTResult<TFrom, TTo>(
TFrom t, DefaultMapConfig defaultMapConfig = null) { var mapper = ObjectMapperManager.DefaultInstance.GetMapper<TFrom, TTo>( defaultMapConfig ?? new DefaultMapConfig()); return mapper.Map(t); } public static TTo ConvertTo<TFrom, TTo>(
this TFrom t, DefaultMapConfig defaultMapConfig = null) { var mapper = ObjectMapperManager.DefaultInstance.GetMapper<TFrom, TTo>( defaultMapConfig ?? new DefaultMapConfig()); return mapper.Map(t); } public static T Convert<T>(DataTable tb) where T : class { var lst = DataTableToEntity<T>(tb); return lst.Any() ? lst.FirstOrDefault() : default(T); } public static List<T> ConvertEnumToList<T>() where T : struct { return Enum.GetValues(typeof(T)).Cast<T>().ToList(); } /// <summary> /// 将datatable转为实体 /// </summary> public static IList<T> DataTableToEntity<T>(DataTable tb) where T : class { if (!HasRows(tb)) return new List<T>(); var lst = new List<T>(); foreach (DataRow item in tb.Rows) { var t = Activator.CreateInstance<T>(); foreach (DataColumn col in tb.Columns) { //先判断属性 var pi = t.GetType().GetProperty(col.ColumnName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); //再判断列 var objValue = item[col.ColumnName]; if (pi != null && pi.PropertyType == col.DataType && pi.CanWrite && (objValue != null && objValue != DBNull.Value)) { pi.SetValue(t, objValue, null); } } lst.Add(t); } return lst; } /// <summary> /// 将列头含有特殊字符的datatable转为实体 /// </summary> /// <typeparam name="T">目标类型</typeparam> /// <param name="tb">原数据table</param> public static IList<T> DataTableToEntityWithColumnChar<T>(DataTable tb) where T : class { if (!HasRows(tb)) return new List<T>(); var lst = new List<T>(); foreach (DataRow item in tb.Rows) { var t = Activator.CreateInstance<T>(); foreach (DataColumn col in tb.Columns) { var tbColumnName = col.ColumnName; var entityPropertyName = ReplaceSpecialChar(tbColumnName); //先判断属性 var pi = t.GetType().GetProperty(entityPropertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); //再判断列 var objValue = item[tbColumnName]; if (pi != null && pi.PropertyType == col.DataType && pi.CanWrite && (objValue != null && objValue != DBNull.Value)) { pi.SetValue(t, objValue, null); } } lst.Add(t); } return lst; }
【CloseableTabItem】
public delegate void CloseButtonDelegate(object sender, RoutedEventArgs e); /// <summary> /// 选项卡TabItem关闭时事件 /// </summary> public event RoutedEventHandler TabItemClosing; /// <summary> /// 实例化标签项 /// </summary> public CloseableTabItem(): this("New tab"){} /// <summary> /// 实例化标签项(给定标题) /// </summary> /// <param name="title">新标签项的标题字符串</param> public CloseableTabItem(string title) : this(title, SortWay.NotSort){} /// <summary> /// 实例化标签项(给定标题,给定是否自动排序) /// </summary> /// <param name="title">标题</param> /// <param name="sortWay"></param> public CloseableTabItem(string title, SortWay sortWay) { //设定样式 Style = (Style) Application.Current.Resources["TabItemStyle"]; //生产一个可关闭的Header var ctih = CreateCloseableTabItem(); //自动排序 switch (sortWay) { case SortWay.IsAutoSort: break; case SortWay.NotSort: break; } //设定标题 ctih.Title = title; //设定名称 Name = title; //设定Header Header = ctih; } /// <summary> /// 创建一个标签页头 /// </summary> /// <returns>标签页头(自定义控件)</returns> private PageTabItemHeader CreateCloseableTabItem() { //实例化一个Header var ctih = new PageTabItemHeader(); //添加关闭按钮点击事件 ctih.BtnClose.Click += btnClose_Click; //返回Header return ctih; } /// <summary> /// 关闭按钮的点击事件处理方法 /// </summary> private void btnClose_Click(object sender, RoutedEventArgs e) { // 触发标签项关闭事件 TabItemClosing?.Invoke(sender, e); if (!e.Handled) { //关闭当前TabItem GetParentObject<TabControl>(this).Items.Remove(this); } } /// <summary> /// 获得指定元素的父元素 /// </summary> /// <typeparam name="T">父级元素类型</typeparam> /// <param name="obj">指定查找元素</param> public T GetParentObject<T>(DependencyObject obj) where T : FrameworkElement { //返回可视对象的父对象 DependencyObject parent = VisualTreeHelper.GetParent(obj); //按层、类型提取父级 while (parent != null) { if (parent is T) return (T) parent; parent = VisualTreeHelper.GetParent(parent); } //返回 return null; }
Tab标签页制作跳转
前端: <TabControl Name="TabControlGrid" TabStripPlacement="Top"
Width="{Binding ElementName=Grid,Path=ActualWidth}"
Height="{Binding ElementName=Grid,Path=ActualHeight}" > <TabItem Header="Search"> <Frame Source="Content/Search.xaml" NavigationUIVisibility="Hidden"> <Frame.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="DarkSeaGreen" Offset="0"/> <GradientStop Color="White" Offset="1"/> </LinearGradientBrush> </Frame.Background> </Frame> </TabItem> </TabControl> 后端: /// <summary> /// 添加用户控件 /// </summary> private CloseableTabItem AddUserControl(IFrameworkInputElement uc) { var ti = new CloseableTabItem(uc.Name) { Content = uc }; TabControlManager.AddTabItem(TabControlGrid, ti); return ti; } /// <summary> /// 添加Page页 /// </summary> private void AddPage(IFrameworkInputElement uc) { var ti = new CloseableTabItem(uc.Name)
{Content = new Frame {Content = uc}}; TabControlManager.AddTabItem(TabControlGrid, ti); } private void EditClick(object sender, RoutedEventArgs e) { AddPage(new Content.Edit()); }
八、项目手册
贴图较多,查阅PPT;
九、下篇预告;
MVC制作站点过程;