WPF +温莎城堡MVVM:定位器-DataContext
编辑:
我找到了一种方法来做到这一点,但我不确定这是否是最好的方法。
在 WindsorContainer
初始化中,首先我注册 viewmodel:
container.Register(Component.For<CentrosViewModel>().LifeStyle.Transient);
然后注册 View
container.Register(Component.For<CentrosAdminView>().LifeStyle.Transient.DependsOn(Property.ForKey("DataContext")
.Eq(ViewModelLocator.Centrosviewmodel)));
并且属性 ViewModelLocator.Centrosviewmodel
的定义是:
public static CentrosModel Centrosviewmodel
{
get
{
return App.container.Resolve<CentrosViewModel>();
}
}
End Edit
我是尝试使用 Castle Windsor 和 Mvvm Toolkit (galasoft) 制作 Wpf 应用程序,但我认为我的问题与任何 MVVM 工具包都是相同的。
使用 MVVM,您必须将视图的 DataContext 设置为 ViewModel。通常,这是通过视图声明中类似的方法来完成的,
DataContext={Binding MyViewModelInstanceName,Source={StaticResource Locator}}
资源定位器在 App.xaml 中定义如下:
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator" />
</Application.Resources>
如果我在 App.xaml 中为我的视图建立 StartupURI,一切都是正确的。 但是,如果我将 StartupUri 留空,并尝试使用以下语法通过 castle 获取视图的实例:
container.Resolve<CentrosAdminView>().Show();
我会得到异常: "Cannot Find Resource with Name '{Locator}'
我认为初始 DataContext 是直接运行时与通过 Castle Windsor 运行时不同,这就是它找不到资源的原因。
我的两个问题是:
- 使用 Castle Windsor 时是否需要有 ViewModelLocator?
- 如果是:如何设置 DataContext 。使用
- Windsor 可以正确查看吗?如果没有:正确的方法是什么?
我将不胜感激。
我的 Windsor 配置如下所示
<castle>
<properties>
<!-- SQL Server settings -->
<connectionString>Server=192.168.69.69;Database=GIOFACTMVVM;user id=sa;password=22336655</connectionString>
<nhibernateDriver>NHibernate.Driver.SqlClientDriver</nhibernateDriver>
<nhibernateDialect>NHibernate.Dialect.MsSql2005Dialect</nhibernateDialect>
</properties>
<facilities>
<facility id="nhibernatefacility"
type="Repository.Infrastructure.ContextualNHibernateFacility, Repository">
<factory id="sessionFactory1">
<settings>
<item key="connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
<item key="connection.driver_class">#{nhibernateDriver}</item>
<item key="connection.connection_string">#{connectionString}</item>
<item key="dialect">#{nhibernateDialect}</item>
<item key="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</item>
</settings>
<assemblies>
<assembly>Domain</assembly>
<assembly>ObservableCollections</assembly>
</assemblies>
</factory>
</facility>
</facilities>
</castle>
:
public static IWindsorContainer Start()
{
var container = new WindsorContainer(new XmlInterpreter());
container.AddFacility<TransactionFacility>();
container.Register(
Component.For<HistoriasAdminView>().LifeStyle.Transient,
Component.For<HistoriasModel>().LifeStyle.Transient,
Component.For<CentrosModel>().LifeStyle.Transient,
Component.For<CentrosAdminView>().LifeStyle.Transient,
Component.For<MainViewModel>().LifeStyle.Transient,
Component.For<MainWindow>().LifeStyle.Transient,
Component.For<IRepository<Historias>>().ImplementedBy<Repository<Historias>>().LifeStyle.Transient,
Component.For<IRepository<Centros>>().ImplementedBy<Repository<Centros>>().LifeStyle.Transient,
Component.For<IUnitOfWork>().ImplementedBy<NHUnitOfWork>().LifeStyle.Transient
);
return container;
}
Edit:
I have found one method to do this but I'm not sure if it is the best way.
In WindsorContainer
initialization, first I register viewmodel:
container.Register(Component.For<CentrosViewModel>().LifeStyle.Transient);
and later I register the View
container.Register(Component.For<CentrosAdminView>().LifeStyle.Transient.DependsOn(Property.ForKey("DataContext")
.Eq(ViewModelLocator.Centrosviewmodel)));
And definition of property ViewModelLocator.Centrosviewmodel
is:
public static CentrosModel Centrosviewmodel
{
get
{
return App.container.Resolve<CentrosViewModel>();
}
}
End Edit
I'm trying to make an Wpf application using Castle Windsor and Mvvm Toolkit (galasoft) but I thing my problem will be the same with any MVVM toolkit.
With MVVM you must set the DataContext of the View to your ViewModel. Normally this is done by something like this in declaration of the view
DataContext={Binding MyViewModelInstanceName,Source={StaticResource Locator}}
Resource Locator is defined in App.xaml like this:
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator" />
</Application.Resources>
If I establish StartupURI in App.xaml to my view all is right.
But if I leave StartupUri blank and I try to get an instance of my view through castle using following syntax:
container.Resolve<CentrosAdminView>().Show();
I get exception: "Cannot Find Resource with Name '{Locator}'
I supose that Initial DataContext is different when running directly than when running through Castle Windsor and this is the reason why it can't find resource.
My two questions are:
- Is It necessary have a ViewModelLocator when using Castle Windsor? In
- case of Yes: How can I setup DataContext of Views correctly with
- Windsor? In case of No: How would be the right way?
I leave down my Castle Configuration. Any help would be really appreciated.
My Windsor configuration look like this:
<castle>
<properties>
<!-- SQL Server settings -->
<connectionString>Server=192.168.69.69;Database=GIOFACTMVVM;user id=sa;password=22336655</connectionString>
<nhibernateDriver>NHibernate.Driver.SqlClientDriver</nhibernateDriver>
<nhibernateDialect>NHibernate.Dialect.MsSql2005Dialect</nhibernateDialect>
</properties>
<facilities>
<facility id="nhibernatefacility"
type="Repository.Infrastructure.ContextualNHibernateFacility, Repository">
<factory id="sessionFactory1">
<settings>
<item key="connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
<item key="connection.driver_class">#{nhibernateDriver}</item>
<item key="connection.connection_string">#{connectionString}</item>
<item key="dialect">#{nhibernateDialect}</item>
<item key="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</item>
</settings>
<assemblies>
<assembly>Domain</assembly>
<assembly>ObservableCollections</assembly>
</assemblies>
</factory>
</facility>
</facilities>
</castle>
and by code:
public static IWindsorContainer Start()
{
var container = new WindsorContainer(new XmlInterpreter());
container.AddFacility<TransactionFacility>();
container.Register(
Component.For<HistoriasAdminView>().LifeStyle.Transient,
Component.For<HistoriasModel>().LifeStyle.Transient,
Component.For<CentrosModel>().LifeStyle.Transient,
Component.For<CentrosAdminView>().LifeStyle.Transient,
Component.For<MainViewModel>().LifeStyle.Transient,
Component.For<MainWindow>().LifeStyle.Transient,
Component.For<IRepository<Historias>>().ImplementedBy<Repository<Historias>>().LifeStyle.Transient,
Component.For<IRepository<Centros>>().ImplementedBy<Repository<Centros>>().LifeStyle.Transient,
Component.For<IUnitOfWork>().ImplementedBy<NHUnitOfWork>().LifeStyle.Transient
);
return container;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您正在使用服务定位器模式,您可以在其中注册服务,传递对容器的引用,并在代码中显式调用解析。如果您快速浏览一下温莎城堡维基,他们不鼓励这种使用容器。
一般来说,您应该注册所有类型(通过安装程序),仅解析一个根对象(可能是您的主视图,可能是某种启动/MVC 控制器样式代码),然后让容器解析其余部分。当您的应用程序退出时,您下次调用容器的时间几乎总是
container.Dispose
。请参阅有关三调用模式的 Windsor wiki 页面。
如果您发现需要在运行时从容器中拉取以创建特定实例(您必须传递特定参数来创建该实例),请使用 类型化工厂设施,而不是直接解析依赖项。
MVVM with Windsor:
在我用 Windsor 编写的第一个 MVVM 应用程序(刚刚完成第一个版本)中,我只是注册了我的主视图和视图模型,而没有指定生活方式。这默认它们是单例。
该视图将视图模型实例作为必需的依赖项(在构造函数中),并使用它来设置数据上下文。我在代码隐藏中执行此操作,因为它是非侵入性且无痛的。
当我有想要创建多个实例的子视图时,我用瞬态生命周期注册了它们。然后我创建了视图工厂和视图模型工厂,并使用它们从父视图获取子视图和子视图模型的实例。我为视图的关闭事件注册了一个事件处理程序,并在工厂类上调用了
Release
方法。最后,对容器的唯一调用是:
所有这些繁琐的事情的好处是它独立于容器(并且可以在没有容器的情况下使用),很明显我的每个组件都采用哪些依赖项,我的大部分代码都不需要询问其依赖项。依赖项只是根据需要传递给每个组件。
You are using the Service Locator pattern, where you register services, pass around a reference to the container, and explicitly call resolve all over you code. If you take a quick tour through the Castle Windsor wiki, they discourage this use of the container.
Generally, you should register all your types (via installers), resolve only one root object (maybe your main view, maybe some sort of startup/MVC controller style code), and let the rest be resolved by the container. The next time you will call the container will almost always be
container.Dispose
, when your application exits.See the Windsor wiki page regarding the Three Calls Pattern.
If you find cases where you need to pull from the container during runtime to create specific instances (where you have to pass specific parameters to create that instance), use the Typed Factory Facility instead of directly resolving dependencies.
MVVM with Windsor:
In my first MVVM app I wrote with Windsor (just finished the first version), I simply registered my main view and view model without specifying the lifestyle. This defaulted them to be singletons.
The view took a view model instance as a required dependency (in the constructor), and used it to set the data context. I did this in code-behind because it was non-intrusive and painless.
Where I had sub-views that I wanted to create multiple instances of, I registered them with a transient life-cycle. Then I created a view factory and view model factory, and used them to get instances of the sub-view and sub-viewmodel from the parent view. I registered an event handler for the view's close event, and called a
Release
method on the factory classes.At the end of all this, the only calls to the container are:
The benefit to all this rigmarole is that it is independent of the container (and can be used without a container), it is obvious which dependencies each of my components take, and most of my code doesn't ever have to ask for its dependencies. The dependencies are just handed to each component as it needs it.