如果我使用类型转换器将RadioButton绑定到视图模型属性,则每次创建视图时,都会调用前一个ViewModel上的setter,即使视图已卸载且不再存在。以下是重现该问题的最低代码:
1)定义枚举类型:
enum EnumType {
Value1,
Value2,
}
2)定义一个更加坚定的人:
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)定义UserControl(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;
}
多次打开和关闭弹出窗口会产生以下输出(请跟踪哈希码):
打开一个新的弹出窗口......
查看(46418718):: ctor
ViewModel(59312528):: ctor
ViewModel(59312528):: Value :: get
查看(46418718)::已加载
关闭弹出窗口......
查看(46418718)::已卸载
打开一个新的弹出窗口......
查看(58892413):: ctor
ViewModel(61646925):: ctor
ViewModel(61646925):: Value :: get
ViewModel(59312528):: Value :: set
查看(58892413)::已加载
关闭弹出窗口......
查看(58892413)::已卸载
这意味着正在调用在Unloaded视图模型中创建的ViewModel的setter,这有点奇怪。对于x:Bind和Binding,这种行为是相同的。
我想知道是否有关于此行为的解释。
澄清更多信息: 每次都会创建一对全新的视图/视图模型实例,但是在加载新视图时,将调用前一个视图模型实例上的setter。前一个视图实例已卸载,此时甚至不应该存在。 (想想每次都关闭的弹出窗口,并且没有引用旧视图/视图模型的事件。)
答案 0 :(得分:0)
这意味着在Unloaded视图中创建的ViewModel的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来测试此项,并且您会发现在您在UI上选择setter
之前不会调用rdo1
。或者,您可以使用其他控件CheckBox
进行测试,在更新setter
值之前也不会调用IsChecked
。
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;
}
}
如果从View中删除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();
}
}
}