将 ViewModel 绑定到多个 windows

Binding ViewModel to multiple windows

我正在重写我的 windows 表单项目,该项目从 vbnet 到 wpf c# 为剪羊毛活动(别问,这在新西兰是一项巨大的运动)进行评分,但遇到了问题我似乎无法克服。

我有两个windows。一个是来源 window,您可以在其中输入内容(例如当前事件名称),另一个 window 将以闪光方式显示此信息以投影到屏幕上(因此将在第二个monitor) 以及通过网络 XML 传入的一些其他数据。我已将其设置为 MVVM,并将 ViewModel 和模型作为单独的项目。

在我的 Main window 上,我可以很好地绑定控件,如果我在一个文本框中键入内容,如果它绑定到同一内容,它会立即出现在另一个文本框中。 但是,在第二个 window 上,我将一个控件绑定到同一事物并且它没有更新。

我已经在这个问题上兜兜转转了一个星期,网上的每个例子都展示了如何在一个 window 上做,我已经很好地工作了,但是缺少两个window 个示例。

这是我的...

这是在我的 ViewModel 项目中

namespace SheepViewModel
{
public class SheepViewModel : INotifyPropertyChanged


{
    private string _CurrentEventName;
    static SheepViewModel _details;

    public string CurrentEventName
    {
        get { return _CurrentEventName; }
        set
        {
            _CurrentEventName = value;
            OnPropertyChanged("CurrentEventName");
        }
    }

    public static SheepViewModel GetDetails()
    {
        if (_details == null)
            _details = new SheepViewModel();
        return _details;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string prop)
    {
        if (PropertyChanged != null)
             PropertyChanged(this, new PropertyChangedEventArgs(prop));
            Console.WriteLine("Test");
            }     
    }
}

然后我有一个主要的 window,除了一行打开第二个 window 之外没有真正的代码,我们将到达...

 public MainWindow()
    {
        ScoreScreen SW = new ScoreScreen();
        SW.Show();
        InitializeComponent();
    }

然后XAML

<Window x:Class="Sheep_Score_3._1.MainWindow"
    xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:SheepViewModel;assembly=SheepViewModel"
    mc:Ignorable="d"
    Title="MainWindow" Height="433.689" Width="941.194">
<Window.DataContext>
    <vm:SheepViewModel/>
</Window.DataContext>
<Window.Resources>
<Grid Margin="0,0,0,0">
<TextBox x:Name="CurrentEventName" Height="23" Margin="131.01,163.013,0,0" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" Width="327.151" Text="{Binding CurrentEventName, Mode=TwoWay}"/>
    <TextBox Text="{Binding CurrentEventName, Mode=TwoWay}" Margin="39.605,0,0,108.567" Height="49.111" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="399" />
</Grid>

以上代码一切正常,如果我在第一个文本框中键入文本,它会出现在第二个文本框中。如果我在通知部分放一个 console.writeline 然后我可以看到它命中它并更新。

现在我添加第二个 window,设置完全相同...

<Window x:Class="Sheep_Score_3._1.ScoreScreen"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:SheepViewModel;assembly=SheepViewModel"
    mc:Ignorable="d"
    Title="ScoreScreen" Height="300" Width="300">
<Window.DataContext>
    <vm:SheepViewModel/>
</Window.DataContext>
<Grid>
    <TextBox x:Name="textBlock" HorizontalAlignment="Left" Margin="79.374,116.672,0,0" TextWrapping="Wrap" Text="{Binding CurrentEventName, Mode=TwoWay}" VerticalAlignment="Top"/>
</Grid>

同样,这里没有真正的代码。

奇怪的是,如果我以两种方式制作此控件并在其中键入,我可以看到它点击了相同的通知部分,但它没有更新另一个 window。

我不确定我在这里遗漏了什么,因此非常感谢您为我指明正确的方向。

我怀疑每个 window 都在创建自己的 ViewModel 实例。您可以尝试以下方法:

public MainWindow()
{
    InitializeComponent();

    SheepViewModel svm = new SheepViewModel();
    this.DataContext = svm;

    ScoreScreen SW = new ScoreScreen();
    SW.DataContext = svm;
    SW.Show();        
}

您的视图模型定义了一个静态方法来获取单个实例,但您没有使用它来实例化它。目前您的视图模型是使用默认构造函数创建的,这意味着两个 windows 将有单独的副本。

在 InitializeComponent 下面或 OnNavigatedToEvent 中的隐藏代码中创建您的视图模型。

这里有一些代码可以进一步解释:

像这样在两个 windows

中定义一个 ViewModel 属性
property SheepViewModel ViewModel { get; set; }

那么你的构造函数:

public MainWindow()
{
    InitializeComponent();
    ViewModel = SheepViewModel.GetDetails(); // include this line
    ScoreScreen SW = new ScoreScreen();
    SW.Show();
}

同时删除

<vm:SheepViewModel/>

来自 xaml,因为它不是必需的。

那是因为两者 windows 必须共享完全相同的 ViewModel 实例。

你所有的属性都是实例属性,喜欢

public string CurrentEventName { get { // snip

因此每个实例的所有值都是不同的。您正在创建 两个 个实例,每个实例一个 window。

<Window x:Class="Sheep_Score_3._1.MainWindow"
    xmlns:blah="http://inurxamlskippinurschemas.org">
    <Window.DataContext>
        <vm:SheepViewModel/>
    </Window.DataContext>

这是一个例子,这是另一个

<Window x:Class="Sheep_Score_3._1.ScoreScreen"
        xmlns:blah="http://yaddayaddawhocares.derp">
    <Window.DataContext>
        <vm:SheepViewModel/>
    </Window.DataContext>

请记住,xaml 只是 反序列化 到对象图中的标记。您有两个不同的标记文件,它们包含其中描述的所有内容的不同实例。

这并没有错,拥有一个带有实例属性的视图模型也没有错。事实上,这是优于使用静态和静态绑定的首选方式。

幸好答案很简单。您需要将视图模型的 windows 相同实例 都交给

首先,从你们的 windows 中删除所有 <Window.DataContext> 废话。那不适合你。现在,只需将构造函数更改为

public MainWindow()
{
    var viewModel = new SheepViewModel();
    ScoreScreen SW = new ScoreScreen();
    SW.DataContext = viewModel;
    SW.Show();
    InitializeComponent();
    //NOTICE!  After Init is called!
    DataContext = viewModel;
}

大功告成。

我会将 ViewModel 作为应用程序资源

<Application.Resources>
    <VM:ViewModel x:Key="SharedViewModel" />
    ....
</Application.Resources>

然后在每个window中你这样称呼它

DataContext="{StaticResource MainViewModel}"

我对这个概念还是陌生的,我不确定这是否是最优的。虽然它有效!