Unity 的嵌套数据上下文

发布于 2024-10-23 13:54:48 字数 1472 浏览 4 评论 0原文

去年我在大学学习了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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南烟 2024-10-30 13:54:48

MVVM 有很多好处,但根据我的经验,连接视图模型和视图是最大的复杂性之一。

有两种主要方法可以做到这一点:

1:

将视图模型连接到视图。

在这种情况下,MainWindow 的 XAML 包含子控件。在您的情况下,其中一些视图可能会被隐藏(因为您一次只显示一个屏幕)。

视图模型通常通过以下两种方式之一连接到视图:

在后面的代码中,在 InitializeComponents() 调用之后或在 this.Loaded 事件处理程序中, let this.DataContext = container.Resolve();

请注意,在这种情况下,容器需要全局可用。这在使用 Unity 的应用程序中很常见。您询问孩子如何解析像 IStatementWriter 这样的接口。如果容器是全局的,则子视图模型可以简单地调用 container.Resolve();

将视图模型连接到视图的另一种方法是在 XAML 中创建视图模型的实例像这样:

<UserControl ...>
  <UserControl.DataContext>
    <local:MyViewModelType/>
  </UserControl.DataContext>
  ...
</UserControl>

此方法与Unity不兼容。有一些 MVVM 框架允许您解析 XAML 中的类型(我相信 Caliburn 可以)。这些框架通过标记扩展来实现此目的。

2:

将视图连接到视图模型。

这通常是我的首选方法,尽管它使 XAML 树更加复杂。当您需要在主视图模型中执行导航时,此方法非常有效。

在主视图模型中创建子视图模型对象。

public class MainViewModel
{
   public MyViewModelType Model1 { get; private set; }
   public ViewModelType2 Model2 { get; private set; }
   public ViewModelType3 Model3 { get; private set; }

   public MainViewModel()
   {
      // This allows us to use Unity to resolve the view models!
      // We can use a global container or pass it into the constructor of the main view model
      // The dependencies for the child view models could then be resolved in their 
      //    constructors if you don't want to make the container global.
      Model1 = container.Resolve<MyViewModelType>();
      Model2 = container.Resolve<ViewModelType2>();
      Model3 = container.Resolve<ViewModelType3>();

      CurrentViewModel = Model1;
   }

   // You will need to fire property changed notifications here!
   public object CurrentViewModel { get; set; }
}

在主视图中,创建一个或多个内容控件并将内容设置为要显示的视图模型。

<Window ...>
   ...
      <ContentControl Content="{Binding CurrentViewModel}">
         <ContentControl.Resources>
            <DataTemplate DataType="{x:Type local:MyViewModelType}">
               <local:MyViewType/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType2}">
               <local:ViewType2/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType3}">
               <local:ViewType3/>
            </DataTemplate>
         </ContentControl.Resources>
      </ContentControl>
   ...
</Window>

请注意,我们通过 ContentControl 上的数据模板将子视图绑定到视图模型。这些数据模板可以在 Window 级别甚至 Application 级别定义,但我喜欢将它们放在上下文中,以便更容易查看视图的获取方式绑定到视图模型。如果每个 ContentControl 只有一种类型的视图模型,我们可以使用 ContentTemplate 属性而不是使用资源。

编辑:在此方法中,可以使用依赖项注入来解析视图模型,但视图是通过 WPF 的资源解析机制来解析的。它的工作原理如下:

ContentPresenterContentControl 中的基础组件)的内容设置为不是视觉对象(不是从 code>Visual 类),WPF 查找数据模板来显示对象。首先,它使用宿主控件上设置的任何显式数据模板(例如 ContentControl 上的 ContentTemplate 属性)。接下来,它会搜索逻辑树,检查树中每个项目的资源,以查找具有资源键 {x:Type local:OBJECT_TYPE}DataTemplate,其中 OBJECT_TYPE 是内容的数据类型。请注意,在本例中,它会找到我们在本地定义的数据模板。当使用目标类型而不是命名键定义样式、控件模板或数据模板时,该类型将成为键。 WindowApplication 位于逻辑树中,因此如果此处定义的资源/模板不在宿主控件的资源中,也将被找到并解析。

最后一点评论。如果未找到数据模板,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 a this.Loaded event handler, let this.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 call container.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:

<UserControl ...>
  <UserControl.DataContext>
    <local:MyViewModelType/>
  </UserControl.DataContext>
  ...
</UserControl>

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.

public class MainViewModel
{
   public MyViewModelType Model1 { get; private set; }
   public ViewModelType2 Model2 { get; private set; }
   public ViewModelType3 Model3 { get; private set; }

   public MainViewModel()
   {
      // This allows us to use Unity to resolve the view models!
      // We can use a global container or pass it into the constructor of the main view model
      // The dependencies for the child view models could then be resolved in their 
      //    constructors if you don't want to make the container global.
      Model1 = container.Resolve<MyViewModelType>();
      Model2 = container.Resolve<ViewModelType2>();
      Model3 = container.Resolve<ViewModelType3>();

      CurrentViewModel = Model1;
   }

   // You will need to fire property changed notifications here!
   public object CurrentViewModel { get; set; }
}

In the main view, create one or more content controls and set the content(s) to the view models that you want to display.

<Window ...>
   ...
      <ContentControl Content="{Binding CurrentViewModel}">
         <ContentControl.Resources>
            <DataTemplate DataType="{x:Type local:MyViewModelType}">
               <local:MyViewType/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType2}">
               <local:ViewType2/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType3}">
               <local:ViewType3/>
            </DataTemplate>
         </ContentControl.Resources>
      </ContentControl>
   ...
</Window>

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 the Window level or even the Application 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 each ContentControl, we could have used the ContentTemplate 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 the ContentControl) is set to an object that is NOT a visual (not derived from the Visual class), WPF looks for a data template to display the object. First it uses any explicit data templates set on the host control (like the ContentTemplate property on the ContentControl). Next it searches up the logical tree, examining the resources of each item in the tree for a DataTemplate 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. The Window and Application 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. If ToString() is not overridden in some meaningful way, the result is a TextBlock containing the content type.
<--

When you update the CurrentViewModel property on the MainViewModel, 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.

明媚殇 2024-10-30 13:54:48

对于基于选项卡的界面这篇关于 WPF 中 MVVM 模式的经典文章可能非常有用。 (它还提供了一个可下载的示例应用程序。)

将每个选项卡与 UserControl 连接的基本思想如下(只是一个粗略的草图,详细信息在文章中):

MainWindow View 有一个 ContentControl ……

<ContentControl Content="{Binding Path=Workspaces}"
                ContentTemplate="{StaticResource WorkspacesTemplate}" />

它绑定MainWindowViewModel 中的“工作区”集合:

public ObservableCollection<WorkspaceViewModel> Workspaces { get; private set; }

WorkspaceViewModel 充当您想要显示为选项卡的所有 ViewModel 的基类。

WorkspacesTemplate 是一个 DataTemplate,它将 TabControl 绑定到 WorkspaceViewModels 集合:

<DataTemplate x:Key="WorkspacesTemplate">
    <TabControl IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding}" />
    </TabControl>
</DataTemplate>

对于每个特定的 Tab,您都有一个带有 ViewModel 的 UserControl,该 ViewModel 派生自 WorkspaceViewModel ...

public class MySpecialViewModel : WorkspaceViewModel

... 并且与DataTemplate 的 UserControl:

<DataTemplate DataType="{x:Type vm:MySpecialViewModel}" >
    <v:MySpecialUserControl />
</DataTemplate>

现在,如果您想打开一个选项卡,您将在 MainWindowViewModel 中使用一个 Command,它创建属于该选项卡的 ViewModel 并将其添加到 MainWindowViewModel 的 Workspaces 集合中:

void CreateMySpecialViewModel()
{
    MySpecialViewModel workspace = new MySpecialViewModel();
    Workspaces.Add(workspace);
}

其余的工作由 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 ...

<ContentControl Content="{Binding Path=Workspaces}"
                ContentTemplate="{StaticResource WorkspacesTemplate}" />

... which binds to a collection of "Workspaces" in the MainWindowViewModel:

public ObservableCollection<WorkspaceViewModel> Workspaces { get; private set; }

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:

<DataTemplate x:Key="WorkspacesTemplate">
    <TabControl IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding}" />
    </TabControl>
</DataTemplate>

And for every specific Tab you have a UserControl with a ViewModel which derives from WorkspaceViewModel ...

public class MySpecialViewModel : WorkspaceViewModel

... and which is related to the UserControl by a DataTemplate:

<DataTemplate DataType="{x:Type vm:MySpecialViewModel}" >
    <v:MySpecialUserControl />
</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:

void CreateMySpecialViewModel()
{
    MySpecialViewModel workspace = new MySpecialViewModel();
    Workspaces.Add(workspace);
}

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.

纸伞微斜 2024-10-30 13:54:48

在解析从 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 the DataContext property of the view to it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文