2016-01-27 16 views
8

Ich schreibe mein Windows Forms Projekt um, das Scafing für Schafe Scheren Ereignisse (nicht fragen, es ist ein großer Sport in Neuseeland) von Vbnet zu Wpf C# und ein Problem, dass Ich kann mich nicht überwinden.Binding ViewModel zu mehreren Fenstern

Ich habe zwei Fenster. Eines ist das Quellfenster, in das Sie Dinge eingeben (wie der aktuelle Ereignisname), und das andere Fenster zeigt diese Informationen zur Projektion auf einen Bildschirm (also auf einem zweiten Monitor) zusammen mit anderen Daten an via XML über das Netzwerk. Ich habe es als MVVM mit einem ViewModel und einem Model als separate Projekte eingerichtet.

In meinem Hauptfenster kann ich Steuerelemente gut binden und wenn ich ein Textfeld eintippe, erscheint es sofort in einem anderen Textfeld, wenn es an dasselbe gebunden ist. In einem zweiten Fenster habe ich jedoch ein Steuerelement an die gleiche Sache gebunden und es wird nicht aktualisiert.

Ich bin seit einer Woche im Kreis herumgegangen, jedes Beispiel im Netz zeigt, wie man es in einem Fenster macht, das funktioniert, aber es fehlen zwei Fensterbeispiele.

Hier ist, was ich habe ...

Dieses Projekt meines Viewmodels ist

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

Dann habe ich ein Hauptfenster, gibt es keinen wirklichen Code hinter anderen als eine Linie eine zweiter öffnen Fenster, die wir ...

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

Dann wird der 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> 
bekommen

Der obige Code funktioniert einwandfrei, wenn ich Text in das erste Textfeld eintippe, erscheint es im zweiten Textfeld. Wenn ich eine console.writeline in den notify-Teil lege, kann ich sehen, dass es darauf trifft und aktualisiert.

Nun füge ich ein zweites Fenster, Setup genau die gleiche Art und Weise ...

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

Wieder keinen wirklichen Code hinter in diesem.

Die seltsame Sache ist, wenn ich dieses Steuerelement zwei-Wege machen und es eintippen, kann ich sehen, dass es den gleichen Benachrichtigungsabschnitt trifft, aber es aktualisiert das andere Fenster nicht.

Ich bin mir nicht sicher, was ich hier vermisse, also würde jede Hilfe, die mich in die richtige Richtung weist, sehr geschätzt.

Antwort

9

Das ist, weil beide Fenster die gleiche Instanz des ViewModel teilen müssen.

Alle Ihre Eigenschaften sind Instanzeigenschaften, wie

public string CurrentEventName { get { // snip 

und damit alle Werte sind für jede Instanz eindeutig. Sie erstellen zwei Instanzen, eine für jedes Fenster.

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

Das eine Instanz ist, und hier ist die andere

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

Denken Sie daran, XAML ist nur, dass Markup in ein Objektdiagramm deserialisiert ist. Sie haben zwei verschiedene Markup-Dateien, sie enthalten unterschiedliche Instanzen von allem, was in ihnen beschrieben wird.

Es ist nichts falsch daran, und nichts falsch daran, ein Ansichtsmodell mit Instanzeigenschaften zu haben. In der Tat ist das der bevorzugte Weg gegenüber statischen und statischen Bindungen.

Die Antwort ist zum Glück einfach. Sie müssen beide Fenster die gleiche Instanz Ihres Ansichtsmodells übergeben.

Zuerst entfernen Sie alle <Window.DataContext> Unsinn aus beiden Fenstern. Das ist nichts für dich. Jetzt, einfach ändern Sie Ihren Konstruktor zu

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

Und du bist fertig.

+0

Vielen Dank, danke, dies genagelt es. Das macht vollkommen Sinn, ich hatte das Gefühl, dass es zwei getrennte Instanzen waren, aber aus einem Vb-Hintergrund stammend war ich nicht sicher, wie ich sie so verbinden sollte. –

+0

Hahahaha! +1 nur für "yaddayaddawhocares.derp" –

+0

@TobyMills Wenn Sie sich über Instanzen nicht sicher sind, können Sie immer Objekt-IDs verwenden, um sie zu verfolgen. https://blogs.msdn.microsoft.com/zainnab/2010/03/04/make-object-id/ Viel Glück beim Scheren! Oh, und wenn du mich aussprichst, wenn du deine Neuschreibung premierst, wäre das super ***. – Will

1

Ich vermute, dass jedes Fenster seine eigene Instanz des ViewModel erstellt. Sie könnten versuchen, die folgenden:

public MainWindow() 
{ 
    InitializeComponent(); 

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

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

Nicht sicher, ob der DataContext von Xaml in InitializeComponent festgelegt ist, daher kann dieser.DataContext zu diesem Zeitpunkt null sein. – Will

+1

Sie können den Datenkontext nach der Initialisierung festlegen. – TrueEddie

+0

Eh, war nicht sicher, also habe ich eine Minute gebraucht, um es zu überprüfen ... DataContext ist nach InitializeComponent gefüllt, aber vorher nicht, also würde dein Original funktionieren. – Will

0

Ihre Ansichtsmodell definiert eine statische Methode eine einzelne Instanz zu bekommen, aber sie tun es nicht instanziiert verwenden. Momentan wird Ihr Ansichtsmodell mit dem Standardkonstruktor erstellt, was bedeutet, dass die beiden Fenster separate Kopien haben.

Erstellen Sie Ihr Ansichtsmodell im Code hinter entweder InitializeComponent oder OnNavigatedToEvent.

Hier einige Code weiter zu erklären:

ein Ansichtsmodell Eigenschaft definieren in beiden Fenstern

wie so
property SheepViewModel ViewModel { get; set; } 

Dann wird Ihr Baumeister:

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

entfernen auch die

<vm:SheepViewModel/> 

von der XAML, wie es nicht erforderlich ist.

+0

Das ist nicht richtig. Er hat bereits eine Eigenschaft auf seinen beiden Fenstern, die für sein Ansichtsmodell - Window.DataContext - entworfen wurde. – Will

+0

meine Antwort noch einmal lesen Ich gebe an, dass es den Standardkonstruktor aufruft, so dass es nicht so funktioniert, wie OP beabsichtigt hat. Er hat eine statische Methode geschrieben, um die Instanz zu bekommen, die ich ihm nur gezeigt habe. –

+0

Lesen Sie meinen Kommentar noch einmal - Sie sagten, er sollte eine ViewModel-Eigenschaft wie in beiden Fenstern definieren, was spektakulär falsch ist. Das Ansichtsmodell bezieht sich auf 'DataContext', nicht auf irgendeine zufällige Eigenschaft. – Will

0

Ich würde das Ansichtsmodell als Anwendungsressource machen

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

dann in jedem Fenster rufen Sie es wie folgt

DataContext="{StaticResource MainViewModel}" 

ich dieses Konzept noch neu bin, und ich bin nicht sicher, ob das optimal ist. Es funktioniert aber!

Verwandte Themen