欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何搭建新的WPF项目框架

程序员文章站 2023-11-16 23:25:22
下面就wpf项目框架搭建步骤一步一步的分享给大家。 在wpf项目开发中最常用的开发模式无疑是mvvm模式,  mvvm模式开发的好处,在这里就不详细讨论, 还有...

下面就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 项目框架搭建的介绍,并结合代码做剖析,希望对大家有所帮助。