如何搭建新的WPF项目框架
作者:New丶Power 发布时间:2023-09-28 08:18:05
下面就WPF项目框架搭建步骤一步一步的分享给大家。
在WPF项目开发中最常用的开发模式无疑是MVVM模式, MVVM模式开发的好处,在这里就不详细讨论, 还有 本文中所使用MVVMLight框架,为什么使用MVVM框架(1、框架较轻,2、学习成本低、3、适用大多数中小型项目,4、相对于微软的prism框架更容易上手)
下面开始 一步一步 搭建框架
第一步: 利用反射创建VM构造器
public class ViewModelFactory
{
private static Dictionary<string, object> vmMap = new Dictionary<string, object>();<br>
public static T GetViewModel<T>() where T : ViewModelBase
{
Type vmType = typeof(T);
if (vmMap.ContainsKey(vmType.FullName))
{
return (T)vmMap[vmType.FullName];
}
else
{
object vm = Activator.CreateInstance(vmType);
vmMap.Add(vmType.FullName, vm);
return (T)vm;
}
}
public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase
{
Type vmType = typeof(T);
if (vmMap.ContainsKey(id))
{
return (T)vmMap[id];
}
else
{
object vm = Activator.CreateInstance(vmType, data);
vmMap.Add(id, vm);
return (T)vm;
}
}
}
为什么用一个Dictionary 将ViewModel 缓存起来,相信利用MVVM模式开发大多数的开发者碰到的问题无疑是各个VM之间的数据通信问题,利用Dictionary缓存起来有两个好处:
1、可以解决VM之间相互通信的问题(当然你也可以用MvvmLight的 Message机制来通信,PS:个人认为完全没必要用MvvmLight中的 Messgae,如果我们框架搭的合理完全可以规避去用MvvmLight中 Message,Message比较难于管理,如果在我们的代码中出现大量的Message无疑是一件痛苦的事情,所以笔者不推荐用MvvmLight中的Message)
2、如果我们的应用程序要频繁的与服务器做交互,我们完全可以用缓存,以避免每次都去请求服务器(可以缓存一些在应用程序中一直使用的数据,规避二次请求)
public static T GetViewModel<T>() where T : ViewModelBase 这个函数(将我们的VM完全限定名作为KEY缓存)适用于单例模式的VM,
public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase 这个函数(主要构件带参数的VM构造函数,id是唯一ID),为什么会用到它,举个例子
例如我们的QQ聊天窗口,所有聊天窗口基本相同用到的VM类型也是相同,所以这时候就需要多个VM实例了,第一种方法就行不通了 所以会用到这种方法去构建VM,并将id作为KEY值缓存起来
第二步:构建我们的ViewModel 基类:
public delegate void CloseEventHandle(object sender);
public class CustomViewModel : ViewModelBase
{
public event CloseEventHandle CloseEvent;
protected bool hasData;
public CustomViewModel()
{
LoadCommand = new RelayCommand(() =>
{
if (!hasData)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
lock (this)
{
OnLoad();
hasData = true;
}
});
}
});
}public RelayCommand LoadCommand { private set; get; }
protected virtual void OnLoad()
{
}
protected void OnClose(object sender)
{
if (sender != null && CloseEvent != null)
{
CloseEvent(sender);
}
}
}
上面CustomViewModel 继承的ViewModelBase 是MvvmLight中的ViewModelBase,至于MvvmLight用法不在本文中讨论,
1、为什么要声明LoadCommand,因为大多数的时候我们会在窗体或用户控件Loaded的时候去加载数据,有可能是异步加载,也有可能是同步加载,所以我们在CustomViewModel中
声明省去了各个VM子类中去声明LoadCommand的麻烦,使用时我们直接在XAML利用MvvmLight提供的EventToCommand 去绑定LoadCommand,然后在对应的VM去重写CustomViewModel基类中的OnLoad方法就可以了。
2、CloseEvent 故名思议是用来在VM中关闭窗体用的(详细用法会在下文中讨论)
3、我们也可以将一些公有的数据都提炼到VM中来。
第三步 管理窗口:
在开发程序的时候我们通常要去管理窗口的如果你没用到MVVM模式 或者是传统的Winform 你可以随便的去new Window(),或者随便的去改Window的构造函数,或者随意的去构造单例窗体,但是如果用到了MVVM模式似乎以上所说的一切都变得复杂了,刚开始的时候我也是挺伤脑筋的,后来在不断的重构代码中找到了解决方法,(PS:本人也是一名菜鸟,只想把自己在开发中的问题及解决方法分享出来,未必就是好的解决方案,所以大神们勿喷)下面上代码: 构建我们的ShowHelper类:
public class ShowHelper
{
private static Dictionary<string, Window> windowManager = new Dictionary<string, Window>();
public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl
{
Type controlType = typeof(T);
string key;
if (constructors == null) //如果构造参数为null
{
key = controlType.FullName; //key = T 的完全限定名
}
else
{
// 如果不为空 并且 第二个构造参数为string(第二个参数代表id -->有可能是GroupId 有可能是UserId);
if (constructors.Length == 2 && constructors[1] is string) //ps:这里本人写死了可以根据需求自行修改
{
key = controlType.FullName + constructors[1].ToString(); //key = 控件 完全限定名+id;
}
else //不满足条件
{
key = controlType.FullName; //key = 限定名
}
}
if (windowManager.ContainsKey(key)) //如果包含KEY
{
windowManager[key].Topmost = true; //设置TopMost
return;
}
UserControl content;
if (constructors == null)
{
content = Activator.CreateInstance(controlType) as UserControl;
}
else
{
content = Activator.CreateInstance(controlType, constructors) as UserControl;
}
BaseWindow window = new BaseWindow(); //PS这是自己封装 的Window,(可以用直接用原始的Wpf Widnow)
window.Title = title;
windowManager.Add(key, window);
window.Closed += (sen, cloE) =>
{
windowManager.Remove(key);
};
if (isDialog)
{
window.ShowDialog();
}
else
{
window.Show();
}
#region 注册关闭事件
if (content.DataContext as CustomViewModel != null)
{
CustomViewModel vm = content.DataContext as CustomViewModel;
vm.CloseEvent += (obj) =>
{
if (content.DataContext.Equals(obj))
{
window.Close();
}
};
}
#endregion
}
public static CustomDialogResult ShowOkCancleUC<T>(string title, MsgBoxBtn okCancle, out object data) where T : Control
{
Type vmType = typeof(T);
Control content = Activator.CreateInstance(vmType) as Control;
OkCanleWindow window = new OkCanleWindow();
window.ShowInTaskbar = false;
return window.ShowDialog(title, okCancle, content, out data);
}
public static CustomDialogResult MessageBoxDialog(string title, string message, MsgBoxBtn okCancle)
{
OkCanleWindow window = new OkCanleWindow();
window.ShowInTaskbar = false;
object none;
return window.ShowDialog(title, okCancle, new MessageUC() { Message = message }, out none);
}
、(1)开始剖析 public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl
ShowDialogUc 是用来在VM中用来创建UserControl并显示在Window中的。你可能会问为啥用windowManager 将窗口缓存起来(PS这里主要还是为了解决单例窗口的麻烦),
至于 下面这段代码,我们可以回到创建的CustomerViewModel中,对这里需要注册VM中CloseEvent事件,这样我们在VM中就可以直接调用OnClose()方法就OK了
#region 注册关闭事件
if (content.DataContext as CustomViewModel != null)
{
CustomViewModel vm = content.DataContext as CustomViewModel;
vm.CloseEvent += (obj) =>
{
if (content.DataContext.Equals(obj))
{
window.Close();
}
};
}
#region 注册关闭事件
(2)开始剖析 public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl 函数中的 constructors 参数
在开始剖析 constructors 之前先让我们 联想一下应用场景(可以先想下,QQ的聊天窗口,例如群聊天吧,所有的群聊天都是相同界面,也就是说他们所对应的VM应该是统一类型的VM,如果我们双击群,则会弹出对应相应的聊天窗口,正常的思维是会给聊天窗口传递参数也就是组ID 这时候我们的VM就需要构造参数了,还有一个问题就是每个群组聊天窗口只能有一个,总不能每次双击就new一个聊天窗口了吧 所以这时候我们就需要做缓存了,) 综上constructors参数在配合ViewModelFactory中的 public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase 方法 可以解决我们VM中需要传递参数的问题,windowManager 可以解决窗口缓存问题(如果你现在还看不明白请 仔细看上面代码(虽然代码有点渣),如果实在看不明白可以在留言板吐槽)。
1、 开始 剖析 public static CustomDialogResult ShowOkCancleUC<T>(string title, MsgBoxBtn okCancle, out object data) where T : Control
(1)开始剖析该函数前让我们 新建一个自己的带返回值的 ShowDialog 窗口
新建xaml窗口
<controls:BaseWindow x:Class="Common.OkCanleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:controls="clr-namespace:Controls;assembly=Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MessageBoxWindow">
<Grid x:Name="grid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="确 定" x:Name="okBtn" Click="okBtn_Click" Grid.Row="1" Height="30" Width="120" HorizontalAlignment="Right" Margin="0 0 10 0"/>
<Button Content="取 消" x:Name="canleBtn" Click="canleBtn_Click" Grid.Row="1" Grid.Column="1" Height="30" Width="120" HorizontalAlignment="Left" Margin="10 0 0 0"/>
</Grid>
</controls:BaseWindow>
后台代码:
public partial class OkCanleWindow : BaseWindow
{
public OkCanleWindow()
{
InitializeComponent();
this.Closed += (s, e) =>
{
if (result == CustomDialogResult.None)
{
result = CustomDialogResult.Cancel;
}
};
}
private System.Windows.Controls.Control control;
CustomDialogResult result;
public CustomDialogResult ShowDialog(string title, MsgBoxBtn btnState, Control uc, out object dataContext)
{
#region 设置控件
if (btnState == MsgBoxBtn.Ok) //如果为OK状态
{
Grid.SetColumnSpan(okBtn, 2); //设置OK按钮跨两列
okBtn.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; //设置OK按钮居中对齐
canleBtn.Visibility = System.Windows.Visibility.Collapsed; //设置Cancel 按钮隐藏;
if (uc != null)
{
control = uc;
Grid.SetRow(uc, 0); //设置控件所在Grid 的行
Grid.SetColumnSpan(uc, 2); //设置控件所在Grid 的列
this.Width = uc.Width; //设置窗体宽度
this.Height = uc.Height + grid.RowDefinitions[1].Height.Value + 35; //设置窗体宽度 高度
grid.Children.Add(uc); //加入控件
}
}
if (btnState == MsgBoxBtn.None) //如果为None 既没有OK 也没有 Cancle
{
grid.RowDefinitions.RemoveAt(1);
okBtn.Visibility = System.Windows.Visibility.Collapsed;
canleBtn.Visibility = System.Windows.Visibility.Hidden;
if(uc !=null)
{
control = uc;
Grid.SetRow(uc, 0); //设置控件所在Grid 的行
Grid.SetColumnSpan(uc, 2); //设置控件所在Grid 的列
this.Width = uc.Width; //设置窗体宽度
this.Height = uc.Height + 35;
grid.Children.Add(uc); //加入控件
}
}
this.Title = title;
dataContext = uc.DataContext;
#endregion
this.ShowDialog();return result;
}
private void okBtn_Click(object sender, RoutedEventArgs e)
{
result = CustomDialogResult.OK;
this.Close();
}
private void canleBtn_Click(object sender, RoutedEventArgs e)
{
result = CustomDialogResult.Cancel;
this.Close();
}
}
public enum CustomDialogResult
{
None,OK,Cancel
}
public enum MsgBoxBtn
{
None,Ok,OkCancel
}
剖析 ShowDialog(string title, MsgBoxBtn btnState, Control uc, out object dataContext) 方法
在Control uc 代表我们要ShowDialog的UC,dataContext 可以输出一些数据,另外我们要自定义一些枚举
public static CustomDialogResult MessageBoxDialog(string title, string message, MsgBoxBtn okCancle) 主要用来显示自定义MessageBoxUserControl;和上面得方法差不多,
以上分为三大步骤对WPF 项目框架搭建的介绍,并结合代码做剖析,希望对大家有所帮助。


猜你喜欢
- 给对象中的包装类设置默认值处理方法如下主要适用于,对象中使用了包装类,但是不能给null需要有默认值的情况/**
- 1.稀疏数组引入1.1 使用场景笔者在课程设计中曾写过一个扫雷小游戏,为了便于讲解,我们来做个简化(实际比这个复杂),只考虑当前位置有雷与无
- 本文实例讲述了Android编程实现全局获取Context及使用Intent传递对象的方法。分享给大家供大家参考,具体如下:一、全局获取 C
- 一、单例模式我们先来看看两种创建单例模式的示例代码。1、饿汉式 饿汉式创建单例模式是在程序里面直接初始化了一个对象实例:class
- 掌握内存操作流输入和输出都是从文件中来的,当然,也可将输出的位置设置在内存上,这就需要ByteArrayInputStream和ByteAr
- 问题描述:图片加载后显示,然后进行删除操作时提示“……正由另一进程使用,因此该进程无法访问该文件。……”解决办法:原代码:iml.Image
- 目录@Configuration + @Bean@Componet + @ComponentScan@Import注解导入@Import直接
- watch机制Zookeeper watch是一种监听通知机制,可以随时监听一些数据的变化,从而实现数据的及时性。Zookeeper所有的读
- 一、前言最近在加强 ITAEM 团队的一个 app 项目——学生教师学习交流平台人员组成:安卓 + 前端 + 后台后台 DAO 层借鉴了华工
- 本文介绍一些Java初学者常问的问题,可以用%除以一个小数吗? a += b 和 a = a + b 的效果有区别吗? 声明一个数组为什么需
- android 实现拨打电话的app,代码非常简单,功能也很实用,分享给大家。MainActivity.javapackage com.bb
- 上篇 《SpringBoot 集成 redis 分布式锁优化》对死锁的问题进行了优化,今天介绍的是 redis 官方推荐使用的 Rediss
- 本文实例为大家分享了C# Winform选项卡集成窗体的具体代码,供大家参考,具体内容如下知识要点:利用反射动态的加载窗体到对应的TabPa
- 新版的IDEA为了防止 pom 更新时,MAVEN 自动导包时卡死的问题,取消了自动导包机制。但新增了导入按钮和快捷键。 问题id
- Android通过wifi连接手机的方法,供大家参考,具体内容如下1.首先电脑,手机连接同一个网络2.在Android studio中Ter
- 一、手机版本问题,大多数文章没有涉及这个点,导致他们的代码并无法正常使用M版本以上需要使用的Type--> TYPE_APPLICAT
- 分类1.简单工厂模式2.工厂方法模式3.抽象工厂模式案例需求根据蛋糕的不同口味,分别创建苹果味和香蕉味的蛋糕实例方案一:简单工厂模式定义蛋糕
- 前言很多人觉得Xamarin的开源少,没法用来开发项目。但,实际上Xamarin已经有很多开源代码了;只要不是特别特殊的项目,基本上是都可以
- 本章概要文件上传@ControllerAdvice文件上传Java 中的文件上传一共涉及两个组件,一个是 CommonsMultipartR
- 同学们应该都去麦当劳或肯德基吃过快餐吧?请同学们参考肯德基官网的信息模拟肯德基快餐店的收银系统,合理使用C++/python/Java,结合