Binding ViewModel to multiple windows

倖福魔咒の 提交于 2021-02-18 05:47:05

问题


I'm re-writing my windows forms project that does scoring for sheep shearing events (don't ask, its a huge sport in New Zealand) from vbnet to wpf c# and have struck a problem that I can't seem to overcome.

I have two windows. One is the source window where you type things in (like the current event name), and the other window will display this information in a flash way for projection onto a screen (so will be on a second monitor) along with some other data coming in via XML over the network. I have set it up as MVVM with a ViewModel and a Model as separate projects.

On my Main window, I can bind controls fine and if I type in one text box it immediately appears in another text box if it is bound to the same thing. However, on a second window, I have bound a control to the same thing and it is not updating.

I've been going around in circles on this for a week, every example on the net shows how to do it on One window which I have got working fine, but there is a lack of two window examples.

Here is what I have...

This is in my ViewModel project

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");
            }     
    }
}

Then I have a main window, there is no real code behind other than a line to open a second window which we will get to...

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

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

The above code all works fine, if I type text in the first textbox it appears in the second text box. If I put a console.writeline in the notify part then I can see it hitting it and updating.

Now I add a second window, setup exactly the same way...

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

Again, no real code behind in this.

The strange thing, is if I make this control two way and type in it, I can see it hitting the same notify section, but it is not updating the other window.

I'm not sure what I am missing here so any help in pointing me in the right direction would be much appreciated.


回答1:


That's because both windows must share the exact same instance of the ViewModel.

All your properties are instance properties, like

public string CurrentEventName { get { // snip

and therefore all values are distinct to each instance. You're creating two instances, one for each window.

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

That's one instance, and here's the other

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

Remember, xaml is just markup that is deserialized into an object graph. You've got two different markup files, they contain distinct instances of everything described within them.

There's nothing wrong with this, and nothing wrong with having a view model with instance properties. In fact, that's the preferred way over using statics and static bindings.

The answer is luckily simple. You need to hand both windows the same instance of your view model.

First, remove all the <Window.DataContext> nonsense from both of your windows. That's not for you. Now, simply change your constructor to

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

And you're done.




回答2:


I suspect that each window is creating its own instance of the ViewModel. You could try the following:

public MainWindow()
{
    InitializeComponent();

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

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



回答3:


Your viewmodel defines a static method to get a single instance but you do not use it to instantiate it. Currently your viewmodel is being created with the default constructor which means the two windows will have separate copies.

Create your viewmodel in the code behind either below InitializeComponent or in the OnNavigatedToEvent.

Here's some code to explain further:

define a ViewModel property like so in both windows

property SheepViewModel ViewModel { get; set; }

Then your constructor:

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

also remove the

<vm:SheepViewModel/>

from the xaml as it's not required.




回答4:


I would make the ViewModel as an application resource

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

then in the each window you call it like this

DataContext="{StaticResource MainViewModel}"

I'm still new to this concept, and I'm not sure if this is optimal. It works though!



来源:https://stackoverflow.com/questions/35046953/binding-viewmodel-to-multiple-windows

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!