如何在XAML中使用DataContext属性将ViewModel设置到窗口上?

112
问题已经表述得很清楚了。
我有一个窗口,并尝试使用ViewModel的完整命名空间设置DataContext,但似乎我做错了什么。
<Window x:Class="BuildAssistantUI.BuildAssistantWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="BuildAssistantUI.ViewModels.MainViewModel">

继续Mike Nakis的话题,我试图手动创建ViewModel并在其中订阅事件,但发现框架正在创建另一个ViewModel。因此,我订阅的viewModel不是附加到视图的那个。 - jlady
这是否意味着除了自己实例化viewmodel之外,您还以其他方式指定了viewmodel的类型?viewmodel需要构造函数参数的第二个优点是,框架要么无法实例化它们,要么必须传递这些参数的默认值,在这种情况下,您可以轻松检测到框架的实例化。 - Mike Nakis
XAML 设计器可能也需要能够实例化视图模型,但这个设计师对我从来没有任何用处(它只会引起问题),所以我不使用它,因此我个人不关心那种用法情况。 - Mike Nakis
DataContext="{Binding Source={x:Type BuildAssistantUI.ViewModels.MainViewModel}}" 这样使用{x:Type}可以吗?但是好像不起作用。 - Vintage Coder
6个回答

162

试试这个。

<Window x:Class="BuildAssistantUI.BuildAssistantWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:VM="clr-namespace:BuildAssistantUI.ViewModels">
    <Window.DataContext>
        <VM:MainViewModel />
    </Window.DataContext>
</Window>

3
我会尽力进行翻译。以下是需要翻译的内容:I like this option the best. Seems cleaner if the VM is only used for the MainWindow.我最喜欢这个选项。如果VM仅用于MainWindow,似乎更加清晰。 - Andrew Grothe
15
жңүжІЎжңүдёҖз§Қж–№жі•еҸҜд»ҘдҪҝз”ЁWindowе…ғзҙ дёҠзҡ„еұһжҖ§жқҘи®ҫзҪ®ж•°жҚ®дёҠдёӢж–ҮпјҢдҫӢеҰӮ DataContext="VM:MainWindowViewModel"пјҹ - Oliver
这是正确的方式! - JavierIEH
我并不完全理解为什么一种方法比另一种更好。而且,与我看到有些人使用“动态资源”的方式相比,我也没有完全看出这两种方式的区别。那么,“动态资源”是什么? - Travis Tubbs
2
@Oliver 你需要实现MarkupExtension,我从未在VMs上实现过它,但你可以使用转换器来确保只有一个转换器实例存在,并直接从xaml中使用="{converters:SomethingConverter}"进行调用,其中xmlns: converters指向转换器命名空间。public abstract class BaseValueConverter<T>:MarkupExtension,IValueConverter where T:class,new(){private static T _converter; public override object ProvideValue(IServiceProvider serviceProvider) {return _converter??(_converter = new T());} } - Whazz
请注意,此机制仅指定 MainViewModel 的类型,而不是 MainViewModel 的实例。这意味着使用此结构,您授权 WPF 自行实例化您的 MainViewModel,这进一步要求您的 MainViewModel 必须具有无参数的构造函数。此外,如果您还在某个地方实例化了 MainViewModel(比如在 App.xaml.cs 中),那么将会有两个实例,而您的视图只会与其中一个交互。 - Mike Nakis

124

除了其他人提供的解决方案(这些解决方案都很好,而且正确),还有一种方法可以在XAML中指定ViewModel,但仍然将特定的ViewModel与视图分开。将它们分开是有用的,当您想编写孤立的测试用例时。

在App.xaml中:

<Application
    x:Class="BuildAssistantUI.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BuildAssistantUI.ViewModels"
    StartupUri="MainWindow.xaml"
    >
    <Application.Resources>
        <local:MainViewModel x:Key="MainViewModel" />
    </Application.Resources>
</Application>

在 MainWindow.xaml 文件中:

<Window x:Class="BuildAssistantUI.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="{StaticResource MainViewModel}"
    />

哦哇...谢谢。我已将此标记为已回答,但您的附加信息非常感谢。我会使用它的。 - Nicholas
@Nicholas:其他答案对这个问题非常完美,所以我同意你的决定。 - Merlyn Morgan-Graham
9
请注意,这种方法对于每个MainWindow实例都使用相同的ViewModel实例。如果窗口是单实例,则可以采用该方法,但如果您需要显示多个窗口实例(例如MDI或选项卡应用程序),则不适用。 - Josh
1
实际上,Josh的答案更好,因为它在DataContext上提供了类型安全。因此,您可以直接绑定到DataContext,而不必担心打错某个属性名称/路径。 - Josh M.
<TextBlock > <TextBlock.DataContext> <local:BaseViewModel/> </TextBlock.DataContext> </TextBlock> This works fine . but, i want like this inline <TextBlock DataContext="{Binding Souce={x:Type local:BaseViewModel}}" /> - Vintage Coder
@VintageCoder 我想你是在尝试提出一个新问题?如果是这样,请继续撰写一个新的独立问题。不过我无法回答它,因为我已经十年没有做任何XAML相关的事情了。 - Merlyn Morgan-Graham

12

你需要实例化MainViewModel并将其设置为数据上下文。在你的语句中,它只被视为字符串值。

     <Window x:Class="BuildAssistantUI.BuildAssistantWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:BuildAssistantUI.ViewModels">
      <Window.DataContext>
        <local:MainViewModel/>
      </Window.DataContext>

谢谢,我想到它正在这样做。 - Nicholas

8

还有一种更好的指定视图模型的方法:

    using Wpf = System.Windows;

    public partial class App : Wpf.Application //your skeleton app already has this.
    {
        protected override void OnStartup( Wpf.StartupEventArgs e ) //add this.
        {
            base.OnStartup( e );
            MainWindow = new MainView();
            MainWindow.DataContext = new MainViewModel( e.Args );
            MainWindow.Show();
        }
    }

上述机制仅适用于主ViewModel。为了回应下面的评论,问及用户控件的情况,该机制对于子ViewModel的翻译如下:
    public class ParentViewModel
    {
        public MyChildViewModel ChildViewModel { get; }
    
        public ParentViewModel()
        {
            ChildViewModel = new MyChildViewModel( ... );
        }
    }

ParentView.xaml:

    [...]
        xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
    [...]
        <local:MyChildView DataContext="{Binding ChildViewModel}" />
    [...]

<抱怨>之前提出的所有解决方案都需要视图模型具有无参构造函数。微软认为可以使用无参数构造函数来构建系统。如果您也持有这种观点,那么请继续使用其他一些解决方案。对于那些了解构造函数必须具有参数,因此对象的实例化不能交由魔幻框架处理的人来说,指定视图模型的正确方式是我上面展示的方法。</抱怨>


如果我有一个包含MainView和MainViewModel的<UserControl>,那么我就没有OnStartup事件,这实际上是我现在正在研究的内容... - PandaWood
@PandaWood,我修改了我的答案以回答你的问题。 - Mike Nakis

3
你可能想尝试 Catel。它允许您定义一个 DataWindow 类(而不是 Window),该类会自动为您创建视图模型。这样,您可以像在原始帖子中一样声明 ViewModel,并且视图模型仍将被创建并设置为 DataContext。
请参见 this article 以获取示例。

0

在xaml中设置它对我没有用。我一遍又一遍地检查我的代码,一切看起来都很好。解决我的问题的是在WindowIsLoaded事件处理程序中设置DataContext:

PrintDrsAnywayViewModel TheWindowsViewModel = new PrintDrsAnywayViewModel();

private void MyWindowIsLoaded(object sender, RoutedEventArgs e)
{
    DataContext = TheWindowsViewModel;
    TheWindowsViewModel.WindowHeaderMessage = "snot";
}

这段 xaml 在工作前后都是一样的:

<TextBlock Text="{Binding WindowHeaderMessage, Mode=OneWay}"
          Margin="20,20,0,20" FontSize="20" />

WindowHeaderMessage是视图模型文件中实现了INotifyPropertyChanged的字符串属性。

希望对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接