问题
I have a UserControl
that contains a 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>
...
Of course, in MyUCVM
, I have TabList
. Now, up to this point, everything works fine.
The problem starts when one of the tabs (e.g. MyTab1) in the TabControl
needs to continuously and recursively read data from some external source (done in the ViewModel
of course), and pass that data to View
(via Binding
) to display. Even up to this point everything is working. However, I do not want that to run when the tab is not visible, because there is no point to do that.
To do that, MyTab1VM
needs to know if the associated View (MyTab1
) is the selected tab. Therefore, I wired this up:
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();
}
}
Unfortunately, this setup only works when I set this.IsSelected = true;
manually in the MyTab1VM
's constructor. Leaving that out in the constructor, the data do not get shown in the view.
I have set breakpoints and confirmed that the binding for IsSelected
is running correctly. Even the timer is running, and ExternalSource.GetData()
is being called. But this.MyData = ExternalSource.GetData();
is not triggering the change from the ViewModel to the View.
The most puzzling part is that the same binding is triggered if IsSelected
is set to true
from the constructor.
Anyone out there knows what happened here?
回答1:
I managed to do some fruitful troubleshooting on my own. I made a breakpoint in SetupToGetData()
and I put this.GetHashCode()
in my debugging watchlist. When I manually set this.IsSelected = true
in the constructor, I realized that the SetupToGetData()
method is called twice, with two different hash values. Planting another breakpoint in the constructor also showed that the constructor is called when I switch to this tab.
I have decided to move this to a new question, because it looks highly possible that the problem has nothing to do with binding.
Edit
Seems like I was right that this is the root of this problem. As that question is solved, so is this as well.
来源:https://stackoverflow.com/questions/37068521/wpf-mvvm-strange-binding-behavior