2016-07-29 8 views
0

Ich habe diesen lustigen Fall: Eine Seite einer ziemlich großen Anwendung hat ein eigenes ViewModel. Ein Teil der Daten stammt aus einer völlig anderen Manager-Klasse. Mein ViewModel hat Zugriff auf diese Daten. Jedes Mal, wenn die Manager-Klasse einen Wert aktualisiert, sendet sie eine Mediator-Nachricht. Das ViewModel ruft die Nachricht ab und ruft OnPropertyChanged(nameof(ManagerdataData)) auf. Die ManagerData-Eigenschaft hat nur einen Getter, um Manager.Data zu erhalten. Die Seite aktualisiert also ihre Sachen. Das System funktioniert so weit.Zweite Ansicht mit ViewModel nicht aktualisieren

Jetzt öffne ich eine MessageBox-Sache (es ist eine System.Window), die MessageBox.DataContext = this.ViewModel bekommt. Für die Benutzeroberfläche funktioniert es auch. Alle meine ManagerData werden geladen und in den verschiedenen Bindungen angezeigt. Aber: OnPropertyChanged scheint keine Wirkung zu haben. Die Seite aktualisiert sich jedes Mal, wenn diese spezielle Mediator-Nachricht aus dem Manager kommt und dann das ViewModel die OnNotificationChanged umdreht, die Binding-Werte werden erneut geladen und neue Daten vom Manager erhalten. Aber die MessageBox hat nicht dasselbe ViewModel.

Hat jemand eine Vorstellung davon, wie das möglich ist?

Ich dachte, vielleicht ist es eine andere Instanz (eine Kopie) meines ViewModel. Also frage ich, ob ein Popup angezeigt wird, hole das oberste Fenster des richtigen Typs und versuche, die Methode aufzurufen, die diese OnNotificationChanged-Werte enthält. Zuerst ist es abgestürzt, weil ich im falschen Thread war. Die Verwendung eines Dispatcher führt zu Einfrieren der gesamten Anwendung.

Irgendeine Idee? Kopieren Code ist ... nicht einfach, wie das Projekt ist ziemlich groß ...

Edit: Okay, also hier ist der Code:

Ansichtsmodell:

// Inside the constructor. Registers for Mediator-message. The manager sends it when a value is set. 
Mediator.Register(MediatorMessage.BackendServerCheckRefreshed,() => RefreshServerCheckUi()); 

// Method of the Mediator Message 
public void RefreshServerCheckUi() 
{ 
    OnPropertyChanged(nameof(BackendServers)); 
    OnPropertyChanged(nameof(BackendServersCommonStatus)); 
} 

// Properties that get stuff from the manager 
public BackendServerStatus BackendServersCommonStatus 
{ 
    get 
    { 
     return backendServerManager.CommonStatus; 
    } 
} 

public BackendServer[] BackendServers 
{ 
    get 
    { 
     return backendServerManager.BackendServers; 
    } 
} 

Die Benutzeroberfläche von dieser Seite:

<!--Style definition--> 
<Style TargetType="Image" x:Key="BackendServerAvailability"> 
    <Style.Triggers> 
     <DataTrigger Binding="{Binding BackendServersCommonStatus}" 
        Value="{x:Static server:BackendServerStatus.NotAvailable}"> 
      <Setter Property="Source" Value="inactive.png" /> 
     </DataTrigger> 
     <DataTrigger Binding="{Binding BackendServersCommonStatus}" 
        Value="{x:Static server:BackendServerStatus.Available}"> 
      <Setter Property="Source" Value="active.png" /> 
     </DataTrigger> 
    </Style.Triggers> 
    <Style.Setters> 
     <Setter Property="Source" Value="default.png" /> 
    </Style.Setters> 
</Style> 

<!--Image--> 
<Image Style="{StaticResource BackendServerAvailability}" /> 

Diese Bindung funktioniert. Wenn der Wert aktualisiert wird, sendet er die Mediator Message, diese ruft OnPropertyChanged auf und dann erhält das Icon sein Image.

Jetzt kommt der schwierige Teil:

// This is how I call the MessageBox from the ViewModel 
this.MessageService.ShowBackendServerCheckInfo(this); 

Die MessageBoxService:

// All MessageBoxes are created like this. 
public void ShowBackendServerCheckInfo(ViewModel viewModel) 
{ 
    Action action = new Action(() => 
    { 
     var args = new MessageBoxEventArgs() 
     { 
      View = new BackendServerCheckMessageBox(), 
      ViewModel = viewModel 
     }; 

     OnNewMessageBox(this, args); 
    }); 
    this.ShowMessageBox(action); 
} 

// And then called like this: 
private void ShowMessageBox(Action action) 
{ 
    /* We need to create the view in the same thread as main application. */ 
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action); 
} 

schließlich in der MainWindow.xaml.cs:

private async void MessageService_OnNewMessageBox(object sender, MessageBoxEventArgs e) 
{ 
    // Some Semaphore work here.. 

    var messageBox = e.View; 
    messageBox.DataContext = e.ViewModel; 
    messageBox.Owner = this; 

    messageBox.ShowDialog(); 

    // Release Semaphore 
} 

Die MessageBox UI:

<ItemsControlItemsSource="{Binding BackendServers}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
      <Stackpanel> 
       <TextBlock Text="{Binding Url}" /> 
       <TextBlock Text="{Binding Port}" /> 
       <Image Style="{StaticResource BackendServerAvailability}" /> 
      </Stackpanel> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl> 

     <!--Buttons--> 
<Stackpanel> 
    <Button Style="{StaticResource SimpleButtonStyle}" 
      Command="{Binding CloseBackendServerCheckCommand}" 
      CommandParameter="{Binding ElementName=BackendServerMsgBox}"> 
     <TextBlock Foreground="White" Margin="2" FontSize="14" 
        Text="{x:Static const:Resources.ButtonOk}" /> 
    </Button> 
    <Button Style="{StaticResource SimpleButtonStyle}" 
      Command="{Binding RestartBackendServerCheckCommand}"> 
     <TextBlock Foreground="White" Margin="2" FontSize="14" 
        Text="{x:Static const:Resources.ButtonRefresh}" /> 
    </Button> 
</StackPanel> 

Das war also der relevante Code. Wenn die Bindungen nicht funktionierten, hatte ich überhaupt keine Werte und die Tasten funktionierten nicht. Der Close-Button sendet sein Window als Argument und dann wird das args-Window-Objekt geschlossen. Wenn die Bindungen und das Zeug nicht funktionieren, würde nichts passieren. Ich reparierte es so weit. Jetzt sehe ich alle meine Werte. Wenn der Backgroundcheck jedoch eine Nachricht sendet, dass ein Server nicht verfügbar ist, wird das Popup-Image nicht aktualisiert, während das Application-Image dies tut.

Meine Vermutung ist, dass das Übergeben all dieser Sachen als EventArgs einige Kopien machen kann und so die Verbindung zur richtigen ViewModel-Instanz verloren geht ... Also würde ich eine Exception für dieses Popup machen und es direkt in meinem ViewModel erstellen. Wie auch immer, es ist mehr als irgendein "gewöhnliches" Popup, das einfach etwas auf dich wirft und du klickst es weg mit "okay". Es ist in diesem Fall komplexer.

Edit2: Aaaund Aufruf der MessageBox direkt aus dem ViewModel hat nichts geändert. Es funktioniert nicht. Ist es ein Problem, mehr als 1 gebundene Ansicht zu haben?

Edit3: Okay, es tut wirklich Arbeit, wenn ich eine Instanz der Popup und neu eingestellt habe Datacontext aus dem Ansichtsmodell. Also muss ich einen schönen Weg finden, zu erhalten oder die Instanz zu halten ...

+0

Das Ausgabefenster von Visual Studio würde Ihnen wahrscheinlich die Antwort geben, denke ich. Wir können Ihnen nicht ohne einen Code helfen, der ausreicht, um das Verhalten zu replizieren. – nkoniishvt

+0

Was könnte im Ausgabefenster sein? Ich dachte, das könnte eher eine Reihe theoretischer Fragen sein: Glauben Sie, dass die Viewmodel-Instanz nur in Datenkontext kopiert wird? – ecth

+0

* Wenn * Sie genau die Art dargestellt haben, wie das Ansichtsmodell MessageBox.DataContext zugewiesen wird, scheint das Kopieren unwahrscheinlich. Aber es ist schwer, jemandem viel zu sagen, der im Wesentlichen sagt: "Ich werde dir meinen Code nicht zeigen. Bitte rekonstruiere alles, was er tut." Was passiert, ist eindeutig möglich, weil es passiert. Meine erste wilde Vermutung wäre, dass Ihr Problem in etwas liegt, das Sie absolut ausgeschlossen haben, weil Sie einfach wissen, dass es nicht der Fall sein kann. Es ist wahrscheinlich nicht etwas Exotisches. Es ist wahrscheinlich ein herkömmlicher Fehler, den Sie finden, indem Sie alles von Punkt A nach Punkt Z verfolgen. –

Antwort

0

So nach fiddeling um habe ich eine schmutzige Lösung:

// Only if the Popup is shown 
if (isShowingBackendServerMessageBox) 
{ 
    Application.Current.Dispatcher.BeginInvoke(new Action(() => 
    { 
     // Get the frontmost Window 
     var messageBox = Application.Current.Windows 
      ?.OfType<BackendServerCheckMessageBox>() 
      ?.FirstOrDefault(); 

     // If it is the searched MessageBox, reload all Bindings. 
     if (messageBox != null) 
     { 
      messageBox.DataContext = null; 
      messageBox.DataContext = this; 
     } 
    })); 
} 

ich nicht diese Art von Code mögen. Aber das funktioniert und erfrischt, wenn ich es brauche. Wenn jemand eine bessere Idee hat, sind Sie willkommen.