【问题标题】:Binding ViewModel to multiple windows将 ViewModel 绑定到多个窗口
【发布时间】:2016-05-04 23:17:48
【问题描述】:

我正在重新编写我的 windows 窗体项目,该项目为剪羊毛事件(不要问,这是新西兰的一项大型运动)从 vbnet 到 wpf c# 进行评分,并且遇到了一个我似乎无法解决的问题克服。

我有两个窗口。一个是您输入内容的源窗口(如当前事件名称),另一个窗口将以 Flash 方式显示此信息以投影到屏幕上(因此将在第二个监视器上)以及一些其他数据通过网络通过 XML 输入。我已将其设置为 MVVM,并将 ViewModel 和 Model 作为单独的项目。

在我的主窗口上,我可以很好地绑定控件,如果我在一个文本框中键入,如果它绑定到相同的东西,它会立即出现在另一个文本框中。 但是,在第二个窗口上,我已将控件绑定到同一事物,但它没有更新。

我已经在这个圈子里转了一个星期,网上的每个例子都显示了如何在一个我可以正常工作的窗口上做到这一点,但是缺少两个窗口示例。

这就是我所拥有的......

这是在我的 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");
            }     
    }
}

然后我有一个主窗口,后面没有真正的代码,除了一行来打开我们将要打开的第二个窗口...

 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 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>

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

奇怪的是,如果我用两种方式控制这个控件并输入它,我可以看到它点击了同一个通知部分,但它没有更新另一个窗口。

我不确定我在这里缺少什么,因此非常感谢任何帮助我指出正确方向的帮助。

【问题讨论】:

    标签: c# wpf xaml mvvm binding


    【解决方案1】:

    这是因为两个窗口必须共享完全相同的 ViewModel 实例。

    你所有的属性都是实例属性,比如

    public string CurrentEventName { get { // snip
    

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

    <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 只是反序列化到对象图中的标记。您有两个不同的标记文件,它们包含其中描述的所有内容的不同实例。

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

    幸运的是,答案很简单。您需要将您的视图模型的两个窗口交给相同的实例。

    首先,从您的两个窗口中删除所有&lt;Window.DataContext&gt; 废话。那不适合你。现在,只需将您的构造函数更改为

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

    你已经完成了。

    【讨论】:

    • 谢谢谢谢谢谢,这成功了。这很有意义,我感觉它们是两个独立的实例,但来自 vb 背景,我不知道如何像这样链接它们。
    • 哈哈哈哈! +1 只为“yaddayaddawhocares.derp”
    • @TobyMills 如果您不确定实例,您始终可以使用对象 ID 来跟踪它们。 blogs.msdn.microsoft.com/zainnab/2010/03/04/make-object-id 祝剪毛好运!哦,如果你在你的重写首映时大声喊我,那将是超级棒。
    • 会做@Will 比赛将在 5 周后在这里播出...google.com.au/search?q=Golden+shears+TV&ie=&oe=
    • 由于在 XAML 文件中指定 DataContext 将使 Visual Studio 设计器能够在 IntelliSense 中提供 DataContext 的成员,因此您可以说 SW.DataContext = DataContex,并将 &lt;Window.DataContext&gt; 废话留在 MainWindow 中,然后在 XAML 中为另一个窗口指定 d:DataContext 属性。或者只是在两个窗口中指定d:DataContext,但构造函数应该保持@Will 写的那样。
    【解决方案2】:

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

    public MainWindow()
    {
        InitializeComponent();
    
        SheepViewModel svm = new SheepViewModel();
        this.DataContext = svm;
    
        ScoreScreen SW = new ScoreScreen();
        SW.DataContext = svm;
        SW.Show();        
    }
    

    【讨论】:

    • 不确定xaml中的DataContext是否设置在InitializeComponent中,所以此时this.DataContext可能为null。
    • 初始化后可以设置datacontext。
    • 嗯,不确定,所以我花了一分钟来验证它... DataContext 是在 InitializeComponent 之后而不是之前填充的,所以你原来的会起作用。
    【解决方案3】:

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

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

    然后在每个窗口中你都这样称呼它

    DataContext="{StaticResource MainViewModel}"
    

    我还是这个概念的新手,我不确定这是否是最佳的。但它确实有效!

    【讨论】:

      【解决方案4】:

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

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

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

      在两个窗口中定义一个 ViewModel 属性

      property SheepViewModel ViewModel { get; set; }
      

      那么你的构造函数:

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

      也去掉

      <vm:SheepViewModel/>
      

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

      【讨论】:

      • 这不对。他已经在他的两个窗口上获得了一个为他的视图模型设计的属性——Window.DataContext。
      • 再次阅读我的答案,我声明它调用了默认构造函数,因此它不能按 OP 的预期工作,他编写了一个静态方法来获取实例,我只是向他展示了如何使用它。
      • 再次阅读我的评论-您说他应该“在两个窗口中都像这样定义 ViewModel 属性”,这是非常不正确的。视图模型进入DataContext,而不是一些随机属性。
      • ViewModel 不是随机属性,它自 Win 10 发布以来与 WinRT 上的 Binding 和 x:Bind 一起使用。有一些关于 msdn 的精彩文章,例如 Data Binding In Depth,您可能会觉得有趣。跨度>
      • 啊,我看到了混乱... OP 的应用程序是普通的旧 WPF,这意味着绑定与 DataContext 一起工作。您的示例在 UWP 中可能有效,但在此处无效 - 检查他的代码,他正在使用 Bindings。我会很笨拙,建议你去复习古老的 Windows Presentation Foundation 文档,但何必呢?绑定是未来!
      猜你喜欢
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-29
      • 2012-09-05
      • 1970-01-01
      相关资源
      最近更新 更多