如何搭建新的WPF项目框架
下面就WPF项目框架搭建步骤一步一步的分享给大家。
在WPF项目开发中最常用的开发模式无疑是MVVM模式, MVVM模式开发的好处,在这里就不详细讨论,还有本文中所使用MVVMLight框架,为什么使用MVVM框架(1、框架较轻,2、学习成本低、3、适用大多数中小型项目,4、相对于微软的prism框架更容易上手)
下面开始一步一步搭建框架
第一步:利用反射创建VM构造器
publicclassViewModelFactory
{
privatestaticDictionary<string,object>vmMap=newDictionary<string,object>();<br>
publicstaticTGetViewModel<T>()whereT:ViewModelBase
{
TypevmType=typeof(T);
if(vmMap.ContainsKey(vmType.FullName))
{
return(T)vmMap[vmType.FullName];
}
else
{
objectvm=Activator.CreateInstance(vmType);
vmMap.Add(vmType.FullName,vm);
return(T)vm;
}
}
publicstaticTGetViewModel<T>(object[]data,stringid)whereT:ViewModelBase
{
TypevmType=typeof(T);
if(vmMap.ContainsKey(id))
{
return(T)vmMap[id];
}
else
{
objectvm=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、如果我们的应用程序要频繁的与服务器做交互,我们完全可以用缓存,以避免每次都去请求服务器(可以缓存一些在应用程序中一直使用的数据,规避二次请求)
publicstaticTGetViewModel<T>()whereT:ViewModelBase 这个函数(将我们的VM完全限定名作为KEY缓存)适用于单例模式的VM,
publicstaticTGetViewModel<T>(object[]data,stringid)whereT:ViewModelBase这个函数(主要构件带参数的VM构造函数,id是唯一ID),为什么会用到它,举个例子
例如我们的QQ聊天窗口,所有聊天窗口基本相同用到的VM类型也是相同,所以这时候就需要多个VM实例了,第一种方法就行不通了所以会用到这种方法去构建VM,并将id作为KEY值缓存起来
第二步:构建我们的ViewModel基类:
publicdelegatevoidCloseEventHandle(objectsender);
publicclassCustomViewModel:ViewModelBase
{
publiceventCloseEventHandleCloseEvent;
protectedboolhasData;
publicCustomViewModel()
{
LoadCommand=newRelayCommand(()=>
{
if(!hasData)
{
ThreadPool.QueueUserWorkItem((obj)=>
{
lock(this)
{
OnLoad();
hasData=true;
}
});
}
});
}publicRelayCommandLoadCommand{privateset;get;}
protectedvirtualvoidOnLoad()
{
}
protectedvoidOnClose(objectsender)
{
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你可以随便的去newWindow(),或者随便的去改Window的构造函数,或者随意的去构造单例窗体,但是如果用到了MVVM模式似乎以上所说的一切都变得复杂了,刚开始的时候我也是挺伤脑筋的,后来在不断的重构代码中找到了解决方法,(PS:本人也是一名菜鸟,只想把自己在开发中的问题及解决方法分享出来,未必就是好的解决方案,所以大神们勿喷)下面上代码:构建我们的ShowHelper类:
publicclassShowHelper
{
privatestaticDictionary<string,Window>windowManager=newDictionary<string,Window>();
publicstaticvoidShowDiaglogUc<T>(stringtitle,object[]constructors=null,boolisDialog=false)whereT:UserControl
{
TypecontrolType=typeof(T);
stringkey;
if(constructors==null)//如果构造参数为null
{
key=controlType.FullName;//key=T的完全限定名
}
else
{
//如果不为空并且第二个构造参数为string(第二个参数代表id-->有可能是GroupId有可能是UserId);
if(constructors.Length==2&&constructors[1]isstring)//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;
}
UserControlcontent;
if(constructors==null)
{
content=Activator.CreateInstance(controlType)asUserControl;
}
else
{
content=Activator.CreateInstance(controlType,constructors)asUserControl;
}
BaseWindowwindow=newBaseWindow();//PS这是自己封装的Window,(可以用直接用原始的WpfWidnow)
window.Title=title;
windowManager.Add(key,window);
window.Closed+=(sen,cloE)=>
{
windowManager.Remove(key);
};
if(isDialog)
{
window.ShowDialog();
}
else
{
window.Show();
}
#region注册关闭事件
if(content.DataContextasCustomViewModel!=null)
{
CustomViewModelvm=content.DataContextasCustomViewModel;
vm.CloseEvent+=(obj)=>
{
if(content.DataContext.Equals(obj))
{
window.Close();
}
};
}
#endregion
}
publicstaticCustomDialogResultShowOkCancleUC<T>(stringtitle,MsgBoxBtnokCancle,outobjectdata)whereT:Control
{
TypevmType=typeof(T);
Controlcontent=Activator.CreateInstance(vmType)asControl;
OkCanleWindowwindow=newOkCanleWindow();
window.ShowInTaskbar=false;
returnwindow.ShowDialog(title,okCancle,content,outdata);
}
publicstaticCustomDialogResultMessageBoxDialog(stringtitle,stringmessage,MsgBoxBtnokCancle)
{
OkCanleWindowwindow=newOkCanleWindow();
window.ShowInTaskbar=false;
objectnone;
returnwindow.ShowDialog(title,okCancle,newMessageUC(){Message=message},outnone);
}
、(1)开始剖析publicstaticvoidShowDiaglogUc<T>(stringtitle,object[]constructors=null,boolisDialog=false)whereT:UserControl
ShowDialogUc是用来在VM中用来创建UserControl并显示在Window中的。你可能会问为啥用windowManager将窗口缓存起来(PS这里主要还是为了解决单例窗口的麻烦),
至于下面这段代码,我们可以回到创建的CustomerViewModel中,对这里需要注册VM中CloseEvent事件,这样我们在VM中就可以直接调用OnClose()方法就OK了
#region注册关闭事件
if(content.DataContextasCustomViewModel!=null)
{
CustomViewModelvm=content.DataContextasCustomViewModel;
vm.CloseEvent+=(obj)=>
{
if(content.DataContext.Equals(obj))
{
window.Close();
}
};
}
#region注册关闭事件
(2)开始剖析publicstaticvoidShowDiaglogUc<T>(stringtitle,object[]constructors=null,boolisDialog=false)whereT:UserControl函数中的constructors参数
在开始剖析constructors之前先让我们联想一下应用场景(可以先想下,QQ的聊天窗口,例如群聊天吧,所有的群聊天都是相同界面,也就是说他们所对应的VM应该是统一类型的VM,如果我们双击群,则会弹出对应相应的聊天窗口,正常的思维是会给聊天窗口传递参数也就是组ID这时候我们的VM就需要构造参数了,还有一个问题就是每个群组聊天窗口只能有一个,总不能每次双击就new一个聊天窗口了吧所以这时候我们就需要做缓存了,)综上constructors参数在配合ViewModelFactory中的publicstaticTGetViewModel<T>(object[]data,stringid)whereT:ViewModelBase方法 可以解决我们VM中需要传递参数的问题,windowManager可以解决窗口缓存问题(如果你现在还看不明白请仔细看上面代码(虽然代码有点渣),如果实在看不明白可以在留言板吐槽)。
1、开始剖析publicstaticCustomDialogResultShowOkCancleUC<T>(stringtitle,MsgBoxBtnokCancle,outobjectdata)whereT:Control
(1)开始剖析该函数前让我们新建一个自己的带返回值的ShowDialog窗口
新建xaml窗口
<controls:BaseWindowx: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">
<Gridx:Name="grid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinitionHeight="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ButtonContent="确定"x:Name="okBtn"Click="okBtn_Click"Grid.Row="1"Height="30"Width="120"HorizontalAlignment="Right"Margin="00100"/>
<ButtonContent="取消"x:Name="canleBtn"Click="canleBtn_Click"Grid.Row="1"Grid.Column="1"Height="30"Width="120"HorizontalAlignment="Left"Margin="10000"/>
</Grid>
</controls:BaseWindow>
后台代码:
publicpartialclassOkCanleWindow:BaseWindow
{
publicOkCanleWindow()
{
InitializeComponent();
this.Closed+=(s,e)=>
{
if(result==CustomDialogResult.None)
{
result=CustomDialogResult.Cancel;
}
};
}
privateSystem.Windows.Controls.Controlcontrol;
CustomDialogResultresult;
publicCustomDialogResultShowDialog(stringtitle,MsgBoxBtnbtnState,Controluc,outobjectdataContext)
{
#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();returnresult;
}
privatevoidokBtn_Click(objectsender,RoutedEventArgse)
{
result=CustomDialogResult.OK;
this.Close();
}
privatevoidcanleBtn_Click(objectsender,RoutedEventArgse)
{
result=CustomDialogResult.Cancel;
this.Close();
}
}
publicenumCustomDialogResult
{
None,OK,Cancel
}
publicenumMsgBoxBtn
{
None,Ok,OkCancel
}
剖析ShowDialog(stringtitle,MsgBoxBtnbtnState,Controluc,outobjectdataContext)方法
在Controluc代表我们要ShowDialog的UC,dataContext可以输出一些数据,另外我们要自定义一些枚举
publicstaticCustomDialogResultMessageBoxDialog(stringtitle,stringmessage,MsgBoxBtnokCancle) 主要用来显示自定义MessageBoxUserControl;和上面得方法差不多,
以上分为三大步骤对WPF项目框架搭建的介绍,并结合代码做剖析,希望对大家有所帮助。