2016-05-13 5 views
2

Ich habe seltsame Problem angetroffen, die ich nicht verstehen kann. In Haupt-Seite habe ich nur eine Taste, die zweite Seite navigiert und hält mein Modell:Binding und x: Bind Probleme mit TwoWay-Modus

public class Model : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public void RaiseProperty(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); 

    private int index = 0; 
    public int Index 
    { 
     get { Debug.WriteLine($"Getting value {index}"); return index; } 
     set { Debug.WriteLine($"Setting value {value}"); index = value; RaiseProperty(nameof(Index)); } 
    } 
} 

public sealed partial class MainPage : Page 
{ 
    public static Model MyModel = new Model(); 

    public MainPage() 
    { 
     this.InitializeComponent(); 
     SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; 
     SystemNavigationManager.GetForCurrentView().BackRequested += (s, e) => { if (Frame.CanGoBack) { e.Handled = true; Frame.GoBack(); } }; 
    } 

    private void Button_Click(object sender, RoutedEventArgs e) => Frame.Navigate(typeof(BlankPage)); 
} 

Auf den zweiten Seite gibt es nur ComboBox, die in SelectedIndex Bindung Zweiweg hat:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 
    <ComboBox SelectedIndex="{x:Bind MyModel.Index, Mode=TwoWay}"> 
     <x:String>First</x:String> 
     <x:String>Second</x:String> 
     <x:String>Third</x:String> 
    </ComboBox> 
</Grid> 
public sealed partial class BlankPage : Page 
{ 
    public Model MyModel => MainPage.MyModel; 

    public BlankPage() 
    { 
     this.InitializeComponent(); 
     this.Unloaded += (s, e) => Debug.WriteLine("--- page unloaded ---"); 
     DataContext = this; 
    } 
} 

Nichts Außergewöhnliches. Das Problem ist, dass ich zwei verschiedene Ausgänge, wenn ich Binding verwenden und x:Bind, aber das Schlimmste ist, dass nach jeder neuen Navigation auf dieselbe Seite der Unterkunft Getter (und Setter in x:Bind) wird immer mehr mal genannt:

enter image description here

Die alte Seite befindet sich noch im Speicher und ist immer noch Eigentum, das ist verständlich. Wenn wir nach der Rückkehr von der Seite GC.Collect() laufen, beginnen wir mit dem Start.

Aber wenn wir verwenden alte Bindung mit Einweg und Auswahl geänderte Ereignisse:

<ComboBox SelectedIndex="{Binding MyModel.Index, Mode=OneWay}" SelectionChanged="ComboBox_SelectionChanged"> 

zusammen mit dem Eventhandler:

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    if (e.RemovedItems.Count > 0 && e.AddedItems.FirstOrDefault() != null) 
     MyModel.Index = (sender as ComboBox).Items.IndexOf(e.AddedItems.FirstOrDefault()); 
} 

dann wird es 'richtig' arbeiten - nur ein Getter und Setter, egal wie oft wir vorher auf die Seite navigieren.

Also meine Hauptfragen sind:

  • wo dieser Unterschied in Einweg-Zwei-Wege- Bindung kommt?
  • unter Berücksichtigung, dass Einweg Bindung Feuer nur einmal Getter - ist das beschriebene Verhalten der Zweiweg- gewünschte/beabsichtigte?
  • wie Sie mit dieser Zwei-Wege- Bindung im Falle mehrerer Getter/Setter bekommen angerufen werden?

Ein funktionierendes Beispiel können Sie download from here.

+1

Liegt das an Ihrer statischen Modellinstanz? nur meine Vermutung. –

+0

@KiranPaul Nein, mit nicht-statischen verhält es sich genau gleich. Ich bin mir fast sicher, dass es irgendwie mit dem Gedächtnis verbunden ist - wenn ich 'GC.Collect()' feuere, nachdem ich zur Hauptseite zurückgekehrt bin, bin ich wieder am Anfang. Trotzdem weiß ich nicht, warum diese beiden Bindungen so unterschiedlich sind und warum der Getter immer nur einmal aufgerufen wird. – Romasz

Antwort

2

Eigentlich, wenn Sie OneWay Bindung mit dem SectionChanged Ereignis verwenden, werden nur die Setter der Index Eigenschaft wird nach dem Ändern der Auswahl genannt. Die Getter wird nie erreicht, daher sehen Sie nicht mehrere "Wert erhalten ...".

Aber warum ist die Getter nicht aufgerufen ??

Setzen Sie einen Haltepunkt auf dieser Linie -

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); 

Sie werden sehen, dass der Wert von PropertyChangednull ist. So wird die Invoke Methode nie ausgelöst. Ich vermute, dass dies ein Fehler in ComboBox mit traditioneller Bindung sein könnte, die auf OneWay gesetzt wird. Wenn Sie die Auswahl ändern, wird die Bindung unterbrochen, daher lautet die PropertyChangednull. Wenn Sie ändern, um x:Bind zu verwenden, verschwindet dieses Problem.

Wie Sie bereits wissen, sammelt die GC nur bei Bedarf Instanzen von verlassenen Seiten. So gibt es Zeiten, in denen Sie sehen, Index ist an mehreren Stellen referenziert, egal welchen Mechanismus Sie gewählt haben.

Eine Möglichkeit, die Getter und Setter zu garantieren nur einmal aufgerufen ist, die die NavigationCacheMode Ihrer zweiten Page-Enabled/Required zu ändern. Auf diese Weise wird eine einzelne Instanz der Seite sichergestellt.

+1

Ich habe total vergessen über den * NavigationCacheMode *. Auch nette fangen mit dieser Eigenschaft geändert beng null. Danke für die Hilfe. – Romasz

+0

Du bist Willkommen, du hast immer interessante Fragen :) –

0

Auch nachdem Sie von und zu einem neuen BlankPage navigiert haben, sind die anderen Seiten immer noch im Speicher und werden immer noch in Ihrem statischen Modell gebunden, wie @KiranPaul kommentiert.

Nun, wenn Sie, wie Sie kommentiert, in keine statische ändern und immer noch das gleiche Verhalten, ist, weil Sie den gleichen Fehler machen. Auch wenn es nicht statisch ist verwenden Sie immer noch die gleiche Variable von Mainpage. (Ich glaube, es ist nicht möglich, aber dazu führen, es ist nicht statisch)

So alle Seiten, die im Speicher sind, die GC.Collect() -ed havent wird das bekommen PropertyChanged Ereignis ausgelöst. Weil das MyModel immer dasselbe ist.

Versuchen Sie, das sollte funktionieren. Jedes Mal, wenn Sie zur leeren Seite navigieren, instanziieren Sie ein neues Modell und übergeben Ihren Index. Wenn Sie dann die Seite entladen, aktualisieren Sie den Wert im MainPage.Model. Wenn Sie die leere Seite verlassen, sehen Sie nur einen Satz und eine Ausgabe.

public sealed partial class BlankPage : Page 
    { 
     public Model MyModel = new Model() { Index = MainPage.MyModel.Index }; 

     public BlankPage() 
     { 
      this.InitializeComponent(); 
      this.Unloaded += (s, e) => { MainPage.MyModel.Index = MyModel.Index; Debug.WriteLine("--- page unloaded ---"); }; 
      DataContext = this; 
     } 
    } 

enter image description here

Oder, wenn Sie verlassen Blankpage können Sie entweder:

  • Anruf GC.Collect()
  • die MyModel entbinden, wenn Sie die Seite entladen?

Edit:

Mit Binding es auch das gleiche tut, wenn Sie es tun wirklich schnell. Meine Vermutung ist, dass die GC.Collect()

genannt wird, also suchte ich ein bisschen und ich fand dies:

Binding vs. x:Bind, using StaticResource as a default and their differences in DataContext

Eine Antwort sagt:

Der {x: Bind} Markup Verlängerungs- Neu für Windows 10 - ist eine Alternative zu {Binding}. {x: Bind} verfügt nicht über einige der Funktionen von {Binding}, aber es läuft in weniger Zeit und weniger Arbeitsspeicher als {Binding} und unterstützt ein besseres Debugging.

Also Binding funktioniert sicherlich anders, es könnte GC.Collect() oder Unbind es selbst aufrufen ??. Vielleicht haben Sie einen Blick in x:Bind markup

enter image description here

+0

Ein paar Fragen dann: 1) Was ist, wenn ich ein in meiner App definiertes Modell verwenden möchte und es nicht neu erstellen möchte? 2) warum verhalten sich "Binding" und "x: Bind" anders? 3) Warum funktioniert die One-Way-Bindung dann richtig? - immer ein Getter, egal wie oft ich zu einer Seite navigiere (mit statischer und nicht-statischer? – Romasz

+0

@Romasz überprüfe meine Bearbeitung. Wenn du GC_Collect() losbinden oder aufrufen 'Ich denke, du bist in Ordnung. – Stamos

+0

Binden sicher ruft nicht 'GC.Collect()' auf, du kannst es sogar während des Debuggens überprüfen.Auch ich bin nicht sicher, was du damit meinst * es kann sich selbst entbinden *. – Romasz

0

Sie könnten versuchen, tracing zu dieser Bindung an etwas Licht hinzuzufügen.

Auch würde ich raten, diese Zeilen zu tauschen, so dass sie wie folgt aussehen:

DataContext = this; 
this.InitializeComponent(); 

es mit Ihren Bindung verwirren könnte. Wie beim Aufruf von initializeComponent wird der xaml-Tree erstellt, aber für die Bindung wird der alte DataContext verwendet, und Sie ändern sofort DataContext, wodurch das erneute Binden jeder Eigenschaft erzwungen wird.