2016-11-13 168 views
0

如果我使用類型轉換器將RadioButton綁定到視圖模型屬性,則每次創建視圖時,都會調用前一個ViewModel的setter,甚至儘管視圖已卸載並且不應再存在。這裏是最小代碼重現該問題:在創建新視圖時在未加載視圖/視圖模型上綁定火災

1)定義枚舉類型:

enum EnumType { 
    Value1, 
    Value2, 
} 

2)定義一個convereter:

public class EnumTypeToBooleanConverter : IValueConverter { 
    public object Convert(object value, Type targetType, object parameter, string language) { 
     return true; 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, string language) { 
     return EnumType.Value1; 
    } 
} 

3)定義視圖模型:

class ViewModel : INotifyPropertyChanged { 
    private EnumType value; 
    public ViewModel() { 
     Debug.WriteLine(string.Format("ViewModel ({0})::ctor", this.GetHashCode())); 
    } 
    public EnumType Value { 
     get { 
      Debug.WriteLine(string.Format("ViewModel ({0})::Value::get", this.GetHashCode())); 
      return this.value; 
     } 
     set { 
      Debug.WriteLine(string.Format("ViewModel ({0})::Value::set", this.GetHashCode())); 
      if (this.value != value) { 
       this.value = value; 
       this.OnPropertyChanged(); 
      } 
     } 
    } 
    private void OnPropertyChanged([CallerMemberName] string name = null) { 
     if (this.PropertyChanged != null) { 
      var ea = new PropertyChangedEventArgs(name); 
      this.PropertyChanged(this, ea); 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

4)定義一個用戶控件(View.xaml)

<UserControl 
    x:Class="BindingIssue.View" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="using:BindingIssue" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" 
    d:DesignHeight="300" 
    d:DesignWidth="400" 
    x:Name="root"> 

    <UserControl.DataContext> 
     <local:ViewModel x:Name="ViewModel"/> 
    </UserControl.DataContext> 

    <Grid> 
     <ScrollViewer>  
      <StackPanel> 
       <RadioButton x:Name="rdo1" 
          Content="Value1" 
          IsChecked="{Binding Path=Value, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> 
       <Button x:Name="btnClose" 
         Click="btnClose_Click" 
         Content="Close"/> 
      </StackPanel> 
     </ScrollViewer> 
    </Grid> 

5)添加代碼觀的背後:

public View() { 
    Debug.WriteLine(string.Format("View ({0})::ctor", this.GetHashCode())); 
    this.InitializeComponent(); 
    this.Loaded += OnLoaded; 
    this.Unloaded += OnUnloaded; 
} 
private void btnClose_Click(object sender, RoutedEventArgs e) { 
    if (this.Parent is Popup) { 
     Debug.WriteLine("Closing the popup..."); 
     ((Popup)this.Parent).IsOpen = false; 
    } 
} 
private void OnLoaded(object sender, RoutedEventArgs e) { 
    Debug.WriteLine(string.Format("View ({0})::Loaded", this.GetHashCode())); 
    } 
private void OnUnloaded(object sender, RoutedEventArgs e) { 
    Debug.WriteLine(string.Format("View ({0})::Unloaded", this.GetHashCode())); 
} 

6)的MainPage(XAML)

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
     x:Name="Grid"> 
    <Button x:Name="btnNewView" 
       Click="btnNewView_Click" 
       Content="New View" 
       Margin="4"/> 
</Grid> 

7)的事件處理程序添加到的MainPage

private void btnNewView_Click(object sender, RoutedEventArgs e) { 
    Debug.WriteLine("Opening a new popup..."); 
    View view = new View(); 
    view.HorizontalAlignment = HorizontalAlignment.Center; 
    view.VerticalAlignment = VerticalAlignment.Center; 

    Popup popup = new Popup(); 
    popup.Child = view; 
    popup.HorizontalOffset = 300; 
    popup.VerticalOffset = 300; 
    popup.IsOpen = true; 
} 

打開d關閉彈出式菜單多次結果如下輸出(請跟蹤的散列碼):

打開一個新的彈出...

視圖(46418718):: ctor的

視圖模型(59312528) ::男星

視圖模型(59312528)::值::獲得

視圖(46418718)::加載

關閉彈出窗口...

視圖(46418718)::卸載

打開一個新的彈出...

視圖(58892413):: ctor的

視圖模型(61646925) ::男星

視圖模型(61646925)::值::獲得

視圖模型(59312528)::值::設爲

視圖(58892413)::加載

關閉彈出...

視圖(58892413)::卸載

這意味着對於處於無負載視圖模型創建的視圖模型設置器被稱爲是有點怪。對於x:Bind和Binding,這種行爲是相同的。

我想知道是否有關於此行爲的解釋。

明晰更多: 一個全新的一對視圖/視圖模型實例被創建的每個時間,但已在加載新的視圖時,上視圖模型的前一個實例的設置器被調用。視圖的前一個實例已卸載,並且不應該在此時存在。 (認爲​​這是被每次閉合一個彈出的,並且沒有事件的引用舊視圖/視圖模型。)

回答

0

這意味着對於在無負載視圖 模型創建的視圖模型的setter被稱爲是有點奇怪

首先,setter不被調用時,視圖卸載,這是調用時加載視圖。您可以添加Loading事件句柄來驗證這一點。添加加載事件代碼,該代碼view控制的背後如下:

this.Loading += View_Loading;  
    private void View_Loading(FrameworkElement sender, object args) 
    { 
     Debug.WriteLine(string.Format("View ({0})::Loading", this.GetHashCode())); 
    } 

而現在的結果應該是:

Closing the popup... 
View (22452836)::Unloaded 
Opening a new popup... 
View (58892413)::ctor 
ViewModel (61646925)::ctor 
View (58892413)::Loading 
ViewModel (61646925)::Value::get 
ViewModel (19246503)::Value::set 
View (58892413)::Loaded 

其次,我們需要尋找到爲什麼setter被稱爲在這種情況下。 一個是因爲您將綁定模式設置爲TwoWay。如果您按如下方式刪除此屬性,您將看不到setter,因爲ViewModel不需要知道view中的更改。

<RadioButton x:Name="rdo1" Content="Value1" IsChecked="{Binding Path=Value, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, UpdateSourceTrigger=PropertyChanged}"/> 

更多關於裝訂模式的細節請參考this article。另一個原因可能是RadioButton控制的具體情況。可以通過單擊同一組中的另一個RadioButton來清除RadioButton,但不能通過再次單擊來清除它。因此,在將IsChecked屬性設置爲true時,我們認爲組的屬性值已更新。這將觸發TwoWay綁定。在您的情景中,您可以通過將缺省值IsChecked設置爲false來進行測試,如下所示,直到您在用戶界面上選擇rdo1後纔會調用setter。或者您可以使用另一個控件CheckBox進行測試,在IsChecked值更新之前,這也不會調用setter

public class EnumTypeToBooleanConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, string language) 
    { 
     return false; 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, string language) 
    { 
     return EnumType.Value1;  
    } 
} 

如果ScrollViewer中被從視圖 行爲刪除行爲是不一樣就是不一樣的可以說布爾屬性

對於這兩種情況,我還測試了我這邊。結果與上面的輸出相同。因爲我不知道如何綁定Boolean屬性,正如我所提到的,是否調用setter取決於綁定模式是什麼以及是否設置或更新屬性。我關於綁定Boolean的測試代碼如下,它們具有相同的輸出。

View.xaml

<RadioButton x:Name="rdo2" 
      Content="BoolValue" 
      IsChecked="{Binding Path=BoolValue, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> 

轉換器:

public class EnumTypeToBooleanConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, string language) 
    { 
     return true; 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, string language) 
    { 
     //return EnumType.Value1;  
     return true; 
    } 
} 

視圖模型;

private bool boolvalue; 
    public bool BoolValue 
    { 
     get 
     { 
      Debug.WriteLine(string.Format("ViewModel ({0})::boolvalue::get", this.GetHashCode())); 
      return this.boolvalue; 
     } 
     set 
     { 
      Debug.WriteLine(string.Format("ViewModel ({0})::boolvalue::set", this.GetHashCode())); 
      if (this.boolvalue != value) 
      { 
       this.boolvalue = value; 
       this.OnPropertyChanged(); 
      } 
     } 
    } 
+0

謝謝您的答覆,但我認爲你錯過了問題的全部意義:爲什麼制定者正在呼籲「老」視圖模型視圖時的一個新實例(和新的視圖模型)被加載?請注意,我不重新載入舊視圖,每次創建一個「新」視圖和視圖模型,舊視圖在這一點上應該超出範圍(或GC'ed)。而且我沒有說當視圖被卸載時調用者被調用。我說setter正在被調用的未加載視圖/視圖模型。那個觀點模型在這一點上應該已經死了。 –

+0

你是對的滾動查看器的東西,它發生沒有滾動查看器以及。我更新了這個問題,但問題的主要觀點仍未得到答覆。 –