Unity 的嵌套数据上下文
去年我在大学学习了VB.Net + WPF 的课程。对于最终项目,我决定尝试一下 MVVM(我们在课程中根本没有讨论过它,我只是研究了它并认为这将是一个有用的练习)。这是一次很好的经历,但我确信我在设计时可能做出了一些糟糕的选择。
我已经毕业了,我的工作与 WPF 或 Windows 开发无关,但是我正在用自己的时间开发一个小型应用程序,并认为使用 C# 和 WPF 会很有趣(C# 是我非常喜欢的语言)我喜欢使用 WPF,所以这是一个非常合乎逻辑的选择)。
不管怎样,我以此为契机进一步了解 MVVM,并尝试以比以前更好的方式实现它。我阅读了更多内容,发现绘制图表比我在学习 WPF 的同时尝试实现它时要容易得多。
我使用 In The Box MVVM Training 作为指南,并将使用 Unity 进行依赖注入。
现在,在指南中开发的示例应用程序中,有一个视图模型(MainWindowViewModel)。 MainWindow 几乎是一个包含 3 或 4 个 UserControl 的容器,它们都共享 MainWindow 的 DataContext。
在我的应用程序中,我希望有一个基于选项卡的界面。因此,主窗口将主要关注显示用于切换当前视图的按钮列表(即从“添加”视图移动到“列表视图”)。每个视图都是一个独立的 UserControl,它将实现它自己的 DataContext。
应用程序中的相同代码如下:
MainWindow window = container.Resolve<MainWindow>();
window.DataContext = container.Resolve<MainWindowViewModel>();
window.Show();
这对于设置 MainWindow 的数据上下文来说很好,但是我将如何处理将每个用户上下文分配为它自己的 ViewModel 作为 DataContext?
编辑:更具体地说,当我说基于选项卡的界面时,我并不是指文本编辑器或网络浏览器中的选项卡。相反,每个“选项卡”都是应用程序的不同屏幕 - 一次只有一个活动屏幕。
另外,虽然 Slauma 的帖子有些帮助,但它并没有真正解释我如何向这些选项卡注入依赖项。例如,如果需要 NewStatementView 输出其数据,我将如何注入实现“IStatementWriter”接口的类的实例?
编辑:为了简化我的问题,我基本上试图弄清楚如何将依赖项注入到类中,而不通过构造函数传递每个依赖项。作为一个人为的例子: A类有B类。 B 类作为构造函数参数需要接口 I1 的实现。 B类使用C类。 类 C 作为构造函数参数需要接口 I2 的实现。
我将如何使用 DI(和 Unity)处理这种情况?我不想做的是: public class A(I1 i1, I2 i2) { .... }
我可以使用 Unity 注册所有内容(即创建 I2,然后创建 C,然后创建 I1 和 B,最后将它们插入到 A 中),但随后我必须实例化当我想使用 A 时,即使我可能不需要 B 的实例(如果我有一大堆其他类与 B 处于相同的情况怎么办?)。
I took a course on VB.Net + WPF at university last year. For the final project, I decided to give MVVM a go (we hadn't discussed it at all in the course, I had just researched it and thought it would be a useful exercise). It was a good experience however I'm rather sure I might have made some poor choices when it came to design.
I've since graduated and my job has nothing to do with WPF or Windows development however I'm developing a small application in my own time and thought it would be fun to use C# and WPF (C# is a language I very much like to work with and I enjoyed working with WPF so it's a pretty logical choice).
Anyway, I'm using this as an opportunity to learn more about MVVM and try and implement it in a better way than I did previously. I've done a bit more reading and am finding it a lot easier to graph than I had when trying to implement it alongside learning WPF.
I've used In The Box MVVM Training as a guide and will be using Unity for dependency injection at this.
Now, in the sample app developed in the guide, there is a single view model (MainWindowViewModel). The MainWindow is pretty much a container with 3 or 4 UserControls which all share the DataContext of the MainWindow.
In my app, I'd like to have a tab-based interface. As such, the MainWindow will be primary concerned with displaying a list of buttons to switch the current view (i.e. move from the 'add' view to the 'list view'). Each view will be a self-contained UserControl which will implement it's own DataContext.
The same code in the app is as follows:
MainWindow window = container.Resolve<MainWindow>();
window.DataContext = container.Resolve<MainWindowViewModel>();
window.Show();
That's fine for setting data context of the MainWindow, however how will I handle assigning each user context it's own ViewModel as a DataContext?
EDIT: To be more specific, when I say tab-based interface, I don't mean it in the sense of tabs in a text editor or web browser. Rather, each 'tab' is a different screen of the application - there is only a single active screen at a time.
Also, while Slauma's post was somewhat helpful, it didn't really explain how I'd go about injecting dependencies to those tabs. If the NewStatementView, for example, was required to output it's data, how would I inject an instance of a class that implements the 'IStatementWriter' interface?
EDIT: To simplify my question, I'm basically trying to figure out how to inject a dependency to a class without passing every dependency through the constructor. As a contrived example:
Class A has Class B.
Class B takes as a constructor paramater needs an implementation of Interface I1.
Class B uses Class C.
Class C takes as a constructor paramater needs an implementation of Interface I2.
How would I handle this scenario using DI (and Unity)? What I don't want to do is:
public class A(I1 i1, I2 i2) { .... }
I could register everything using Unity (i.e. create I2, then C, then I1 and B, and then finally insert these into A) but then I would have to instantiate everything when I want to use A even if I might not even need an instance of B (and what if I had a whole bunch of other classes in the same situation as B?).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
MVVM 有很多好处,但根据我的经验,连接视图模型和视图是最大的复杂性之一。
有两种主要方法可以做到这一点:
1:
将视图模型连接到视图。
在这种情况下,
MainWindow
的 XAML 包含子控件。在您的情况下,其中一些视图可能会被隐藏(因为您一次只显示一个屏幕)。视图模型通常通过以下两种方式之一连接到视图:
在后面的代码中,在
InitializeComponents()
调用之后或在this.Loaded
事件处理程序中, letthis.DataContext = container.Resolve();
请注意,在这种情况下,容器需要全局可用。这在使用 Unity 的应用程序中很常见。您询问孩子如何解析像 IStatementWriter 这样的接口。如果容器是全局的,则子视图模型可以简单地调用
container.Resolve();
将视图模型连接到视图的另一种方法是在 XAML 中创建视图模型的实例像这样:
此方法与Unity不兼容。有一些 MVVM 框架允许您解析 XAML 中的类型(我相信 Caliburn 可以)。这些框架通过标记扩展来实现此目的。
2:
将视图连接到视图模型。
这通常是我的首选方法,尽管它使 XAML 树更加复杂。当您需要在主视图模型中执行导航时,此方法非常有效。
在主视图模型中创建子视图模型对象。
在主视图中,创建一个或多个内容控件并将内容设置为要显示的视图模型。
请注意,我们通过
ContentControl
上的数据模板将子视图绑定到视图模型。这些数据模板可以在Window
级别甚至Application
级别定义,但我喜欢将它们放在上下文中,以便更容易查看视图的获取方式绑定到视图模型。如果每个ContentControl
只有一种类型的视图模型,我们可以使用ContentTemplate
属性而不是使用资源。编辑:在此方法中,可以使用依赖项注入来解析视图模型,但视图是通过 WPF 的资源解析机制来解析的。它的工作原理如下:
当
ContentPresenter
(ContentControl
中的基础组件)的内容设置为不是视觉对象(不是从code>Visual
类),WPF 查找数据模板来显示对象。首先,它使用宿主控件上设置的任何显式数据模板(例如ContentControl
上的ContentTemplate
属性)。接下来,它会搜索逻辑树,检查树中每个项目的资源,以查找具有资源键{x:Type local:OBJECT_TYPE}
的DataTemplate
,其中 OBJECT_TYPE 是内容的数据类型。请注意,在本例中,它会找到我们在本地定义的数据模板。当使用目标类型而不是命名键定义样式、控件模板或数据模板时,该类型将成为键。Window
和Application
位于逻辑树中,因此如果此处定义的资源/模板不在宿主控件的资源中,也将被找到并解析。最后一点评论。如果未找到数据模板,WPF 会在内容对象上调用
ToString()
并将结果用作可视内容。如果ToString()
未以某种有意义的方式重写,则结果是包含内容类型的TextBlock
。<--
当您更新
MainViewModel
上的CurrentViewModel
属性时,只要您在MainViewModel
上触发属性更改通知,主视图中的内容和视图就会自动更改主视图模型。如果我错过了什么或者您需要更多信息,请告诉我。
MVVM has lots of benefits, but in my experience wiring up the view models and the views is one of the biggest complexities.
There are two main ways to do this:
1:
Wire the view models to the views.
In this scenario, the XAML for the
MainWindow
contains the child controls. In your case, some of these views would probably be hidden (because you are only showing one screen at a time).The view models get wired to the views, usually in one of two ways:
In the code behind, after the
InitializeComponents()
call or in athis.Loaded
event handler, letthis.DataContext = container.Resolve<MyViewModelType>();
Note that in this case the container needs to be globally available. This is typical in applications that use Unity. You asked how children would resolve interfaces like
IStatementWriter
. If the container is global, the child view models could simply callcontainer.Resolve<IStatementWriter>();
Another way to wire the view models into the views is to create an instance of the view model in XAML like this:
This method is not compatible with Unity. There are a few MVVM frameworks that allow you to resolve types in XAML (I believe Caliburn does). These frameworks accomplish this through markup extensions.
2:
Wire the view up to the view model.
This is usually my preferred method, although it makes the XAML tree more complicated. This method works very well when you need to perform navigation in the main view model.
Create the child view model objects in the main view model.
In the main view, create one or more content controls and set the content(s) to the view models that you want to display.
Notice that we tie the child views to the view models through data templates on the
ContentControl
. These data templates could have been defined at theWindow
level or even theApplication
level, but I like to put them in context so that it's easier to see how the views are getting tied to the view models. If we only had one type of view model for eachContentControl
, we could have used theContentTemplate
property instead of using resources.EDIT: In this method, the view models can be resolved using dependency injection, but the views are resolved through WPF's resource resolution mechanism. This is how it works:
When the content for a
ContentPresenter
(an underlying component in theContentControl
) is set to an object that is NOT a visual (not derived from theVisual
class), WPF looks for a data template to display the object. First it uses any explicit data templates set on the host control (like theContentTemplate
property on theContentControl
). Next it searches up the logical tree, examining the resources of each item in the tree for aDataTemplate
with the resource key{x:Type local:OBJECT_TYPE}
, where OBJECT_TYPE is the data type of the content. Note that in this case, it finds the data templates that we defined locally. When a style, control template, or data template is defined with a target type but not a named key, the type becomes the key. TheWindow
andApplication
are in the logical tree, so resources/templates defined here would also be found and resolved if they were not located in the resources of the host control.One final comment. If a data template is not found, WPF calls
ToString()
on the content object and uses the result as the visual content. IfToString()
is not overridden in some meaningful way, the result is aTextBlock
containing the content type.<--
When you update the
CurrentViewModel
property on theMainViewModel
, the content and view in the main view will change automatically as long as you fire the property changed notification on the main view model.Let me know if I missed something or you need more info.
对于基于选项卡的界面这篇关于 WPF 中 MVVM 模式的经典文章可能非常有用。 (它还提供了一个可下载的示例应用程序。)
将每个选项卡与 UserControl 连接的基本思想如下(只是一个粗略的草图,详细信息在文章中):
MainWindow View 有一个 ContentControl ……
它绑定MainWindowViewModel 中的“工作区”集合:
此
WorkspaceViewModel
充当您想要显示为选项卡的所有 ViewModel 的基类。WorkspacesTemplate
是一个 DataTemplate,它将 TabControl 绑定到 WorkspaceViewModels 集合:对于每个特定的 Tab,您都有一个带有 ViewModel 的 UserControl,该 ViewModel 派生自 WorkspaceViewModel ...
... 并且与DataTemplate 的 UserControl:
现在,如果您想打开一个选项卡,您将在 MainWindowViewModel 中使用一个 Command,它创建属于该选项卡的 ViewModel 并将其添加到 MainWindowViewModel 的 Workspaces 集合中:
其余的工作由 WPF 绑定引擎完成。 TabControl 自动识别集合中的这个特殊工作区项目的类型为 MySpecialViewModel,并通过我们定义的 DataTemplate 选择正确的 View/UserControl,以连接 ViewModel 和 View,并将其显示在新选项卡中。
For a Tab-based interface this classical article about MVVM pattern in WPF might be very useful. (It also offers a downloadable sample application.)
The basic idea to connect each tab with a UserControl is as follows (only a rough sketch, details are in the article):
The MainWindow View has a ContentControl ...
... which binds to a collection of "Workspaces" in the MainWindowViewModel:
This
WorkspaceViewModel
serves as a base class for all ViewModels you want to display as a tab.The
WorkspacesTemplate
is a DataTemplate which binds a TabControl to the collection of WorkspaceViewModels:And for every specific Tab you have a UserControl with a ViewModel which derives from WorkspaceViewModel ...
... and which is related to the UserControl by a DataTemplate:
Now, if you want to open a tab you would have a Command in the MainWindowViewModel which creates the ViewModel belonging to that tab and add it to the Workspaces collection of the MainWindowViewModel:
The rest is done by the WPF binding engine. The TabControl recognizes automatically that this special workspace item in the collection is of type
MySpecialViewModel
and selects the right View/UserControl through the DataTemplate we have defined to connect ViewModel and View and displays it in a new Tab.在解析从
UserControl
派生的视图时,请使用 属性注入 为每个视图解析一个新的 ViewModel 并将视图的DataContext
属性设置为其。At the point where you resolve your Views deriving from
UserControl
, use property injection to resolve a new ViewModel for each one and set theDataContext
property of the view to it.