WPF MVVM:奇怪的绑定行为
WPF MVVM: Strange Binding behavior
我有一个 UserControl
,其中包含一个 TabControl
。
<UserControl x:Class="Test.MyUC"
....
xmlns:vm="clr-namespace:Test.ViewModels"
xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
...
<UserControl.Resources>
<vm:MyUCVM x:Key="VM" />
</UserControl.Resources>
<UserControl.DataContext>
<StaticResourceExtension ResourceKey="VM" />
</UserControl.DataContext>
<!-- Using Ivan Krivyakov's Attached Behavior -->
<TabControl ikriv:TabContent.IsCached="True"
TabStripPlacement="Top" ItemsSource="{Binding TabList}" IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:MyTab1VM}">
<v:MyTab1/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyTab2VM}">
<v:MyTab2/>
</DataTemplate>
</TabControl.Resources>
...
当然,在MyUCVM
,我有TabList
。现在,到目前为止,一切正常。
当 TabControl
中的其中一个选项卡(例如 MyTab1)需要连续递归地从某个外部源读取数据(当然是在 ViewModel
中完成)并通过该数据以 View
(通过 Binding
)显示。到目前为止,一切正常。但是,当不可见时,我不希望运行,因为没有意义。
为此,MyTab1VM
需要知道关联的视图 (MyTab1
) 是否是选定的选项卡。因此,我将其连接起来:
MyTab1:
<Style TargetType="TabItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}" />
</Style>
MyTab1VM
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected",
typeof(bool),
typeof(MyTab1VM),
new PropertyMetadata(false, new PropertyChangedCallback(IsSelectedChanged))
);
public bool IsSelected
{
get
{
return (bool) GetValue(IsSelectedProperty);
}
set
{
SetValue(IsSelectedProperty, value);
}
}
public static void IsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.Property == IsSelectedProperty)
{
MyTab1VM vm = d as MyTab1VM ;
vm.SetupToGetData();
}
}
private void SetupToGetData()
{
if (this.IsSelected)
{
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += timer_Tick;
timer.Start();
}
}
private void timer_Tick(object sender, EventArgs e)
{
if (this.IsSelected)
this.MyData = ExternalSource.GetData();
else
{
(sender as System.Windows.Threading.DispatcherTimer).Stop();
}
}
不幸的是,此设置仅在我在 MyTab1VM
的构造函数中手动设置 this.IsSelected = true;
时有效。将其留在构造函数中,数据不会显示在视图中。
我已经设置断点并确认 IsSelected
的绑定是正确的 运行ning。连定时器都运行ning了,正在调用ExternalSource.GetData()
。但是 this.MyData = ExternalSource.GetData();
不会触发从 ViewModel 到 View 的更改。
最令人费解的部分是,如果从构造函数中将 IsSelected
设置为 true
,则会触发相同的绑定。
有人知道这里发生了什么吗?
我自己设法进行了一些富有成效的故障排除。我在 SetupToGetData()
中创建了一个断点,并将 this.GetHashCode()
放入我的调试监视列表中。当我在构造函数中手动设置 this.IsSelected = true
时,我意识到 SetupToGetData()
方法被调用了两次,具有两个不同的哈希值。在构造函数中设置另一个断点也显示当我切换到此选项卡时调用了构造函数。
我决定将其移至新的 ,因为看起来问题很可能与绑定无关。
编辑
看来我是对的, 是这个问题的根源。这个问题解决了,这个也解决了。
我有一个 UserControl
,其中包含一个 TabControl
。
<UserControl x:Class="Test.MyUC"
....
xmlns:vm="clr-namespace:Test.ViewModels"
xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
...
<UserControl.Resources>
<vm:MyUCVM x:Key="VM" />
</UserControl.Resources>
<UserControl.DataContext>
<StaticResourceExtension ResourceKey="VM" />
</UserControl.DataContext>
<!-- Using Ivan Krivyakov's Attached Behavior -->
<TabControl ikriv:TabContent.IsCached="True"
TabStripPlacement="Top" ItemsSource="{Binding TabList}" IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:MyTab1VM}">
<v:MyTab1/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyTab2VM}">
<v:MyTab2/>
</DataTemplate>
</TabControl.Resources>
...
当然,在MyUCVM
,我有TabList
。现在,到目前为止,一切正常。
当 TabControl
中的其中一个选项卡(例如 MyTab1)需要连续递归地从某个外部源读取数据(当然是在 ViewModel
中完成)并通过该数据以 View
(通过 Binding
)显示。到目前为止,一切正常。但是,当不可见时,我不希望运行,因为没有意义。
为此,MyTab1VM
需要知道关联的视图 (MyTab1
) 是否是选定的选项卡。因此,我将其连接起来:
MyTab1:
<Style TargetType="TabItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}" />
</Style>
MyTab1VM
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected",
typeof(bool),
typeof(MyTab1VM),
new PropertyMetadata(false, new PropertyChangedCallback(IsSelectedChanged))
);
public bool IsSelected
{
get
{
return (bool) GetValue(IsSelectedProperty);
}
set
{
SetValue(IsSelectedProperty, value);
}
}
public static void IsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.Property == IsSelectedProperty)
{
MyTab1VM vm = d as MyTab1VM ;
vm.SetupToGetData();
}
}
private void SetupToGetData()
{
if (this.IsSelected)
{
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += timer_Tick;
timer.Start();
}
}
private void timer_Tick(object sender, EventArgs e)
{
if (this.IsSelected)
this.MyData = ExternalSource.GetData();
else
{
(sender as System.Windows.Threading.DispatcherTimer).Stop();
}
}
不幸的是,此设置仅在我在 MyTab1VM
的构造函数中手动设置 this.IsSelected = true;
时有效。将其留在构造函数中,数据不会显示在视图中。
我已经设置断点并确认 IsSelected
的绑定是正确的 运行ning。连定时器都运行ning了,正在调用ExternalSource.GetData()
。但是 this.MyData = ExternalSource.GetData();
不会触发从 ViewModel 到 View 的更改。
最令人费解的部分是,如果从构造函数中将 IsSelected
设置为 true
,则会触发相同的绑定。
有人知道这里发生了什么吗?
我自己设法进行了一些富有成效的故障排除。我在 SetupToGetData()
中创建了一个断点,并将 this.GetHashCode()
放入我的调试监视列表中。当我在构造函数中手动设置 this.IsSelected = true
时,我意识到 SetupToGetData()
方法被调用了两次,具有两个不同的哈希值。在构造函数中设置另一个断点也显示当我切换到此选项卡时调用了构造函数。
我决定将其移至新的
编辑
看来我是对的,