当更改同一视图上的 DataContext 时,Visual Tree 中以前的 DataContext 会发生什么情况?

What happens to previous DataContexts in Visual Tree when DataContext on same View is changed?

我有我的自定义日历控件 - 事件日历。我在某些情况下使用它。

<Controls:EventCalendar Grid.Row="0"
                        Grid.RowSpan="8"
                        Grid.Column="2"
                        Margin="20,50,0,0"
                        CalendarEvents="{Binding DataContext.CalendarEvents, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
                        Header="{Binding DataContext.DataSpis.Header, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
                        ViewModelBase="{Binding DataContext.ViewModel, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
                        IsFunctionalityVisible="{Binding DataContext.IsFunctionalityVisible, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
                        IsCaseLoaded="{Binding DataContext.IsLoaded, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Controls:EventCalendar>

我检测是否通过 IsCaseLoaded 依赖项 属性 加载案例(相同视图,不同数据)。发生这种情况时,我将新的 DataContext 添加到我的日历控件中。像这样:

private static void LoadPCCallback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
  if (((EventCalendar)source).IsCaseLoaded == true)
  {
    ((EventCalendar)source).DataContext = null;
    ((EventCalendar)source).DataContext = new EventCalendarViewModel(((EventCalendar)source).Header, ((EventCalendar)source).ViewModelBase, ((EventCalendar)source).CalendarEvents);
  }
}

EventCalendarViewModel 的构造函数中,我为要显示的会议或任务设置了一些可见性。默认情况下会显示会议并隐藏任务。
当我想显示任务时,我单击此日历控件上的按钮。
现在行为开始变得 意外 :我加载案例,单击任务按钮,它起作用了 - 显示任务,隐藏会议。
我重新加载案例,单击“任务”按钮,它起作用了 - 显示任务,隐藏会议。
但是我第三次重新加载案例(有时是第二次,有时是第四次 - 真的 随机 ),构造函数工作,将会议设置为默认值,但是当我单击任务按钮时,它突然具有以前的值DataContext,所以它认为任务是 true,会议是 false...所以没有任何变化,会议仍然显示。

public void ShowMeetingsButtonClick()
{
  this.ShowTasks = false;
  NotifyOfPropertyChange(() => ShowTasks);
  this.ShowMeetings = true;
  NotifyOfPropertyChange(() => ShowMeetings);
}

Show Tasks也是这样:

public void ShowTasksButtonClick()
{
  this.ShowMeetings = false;
  NotifyOfPropertyChange(() => ShowMeetings);
  this.ShowTasks = true;
  NotifyOfPropertyChange(() => ShowTasks);
}

所以我想到的一件事是,这个日历视图以某种方式在 Visual Tree 中找到了以前的 DataContext 并采用旧值从那里。因为在 new DataContext 的构造函数之后一切看起来都很好,但是在点击一个按钮之后它突然有了不同的值。

我还认为我的一些线程正在改变某些东西,但我尝试对其进行调试,但在此期间没有任何线程(只有主线程)处于活动状态。

好的,我试着重建一些东西来模拟你的行为。并想出了这个,它应该非常接近你正在走向的行为。

我添加了一个 InverseToBooleanConverter 以与布尔相反的方式显示可见性(false = 可见)。这有助于切换东西

我为来自整数的 GridLength(您的身高)添加了一个转换器。我冒昧地创建了一个代表 ShowHide 值的枚举。 重要的规则是让你的 ViewModels 保持纯粹,没有以 System.Windows 或任何与视图相关的东西开头的命名空间。

我以某种方式整理了您的属性和 属性Notification-Stuff。目标是尽可能保持紧实和苗条。所以对于这段代码,我只在 属性 本身内部调用了 On属性Changed。

对我来说,TaskListView 是一个 Control,并且会有自己的 ListOfTaskViewModel(具有行为)和任务集合(取决于其中的复杂性。这也可以成为 ObservableList<TaskItemViewModel>) 这同样适用于 MeetingListViewMeetingListViewModel。 现在重要的是在哪里以及如何加载数据。我可以考虑一个至少有 2 个方法 GetTasksForCaseIDGetMeetingsForCaseID 的服务,它们可以注入到 ViewModel 中,或者可以传递加载的数据。我更喜欢保持独立,并会使用 EventAggregatorMessenger 之类的东西来通知 ViewModel 并将匹配的 ID 作为有效负载。并保持对 ViewModel 获取数据的责任。但这要视情况而定,因为我没有足够的关于您的上下文的信息,所以这超出了示例的范围。但我希望你能明白。 这就是 MainViewModel class

同样的事情也适用于日历中的实际事件和突出显示的内容。它应该在自己的 ViewModel 中与自己的视图控件分开,以保持整洁。

public class MainViewModel:INotifyPropertyChanged
    {
        public MainViewModel()
        {
            Init();
        }

        public enum Calendar{
            ShowCalendarMaxLength = 145,
            HideCalenderHeight = 325,

        }

        private MeetingsListViewModel _listOfMeetingsViewModel;
        public MeetingsListViewModel ListOfMeetingsViewModel {
            get { return _listOfMeetingsViewModel; }
            set
            {
                if (_listOfMeetingsViewModel != value)
                {
                    _listOfMeetingsViewModel = value;
                    OnPropertyChanged("ListOfMeetings");
                }
            }

        }

        public TaskListViewModel _listOfTasksViewModel;
        public TaskListViewModel ListOfTasksViewModel { 
            get{return _listOfTasksViewModel;}
            set {
                if (_listOfTasksViewModel != value)
                {
                    _listOfTasksViewModel = value;
                    OnPropertyChanged("ListOfTasks");
                }
            }
        }

        private Calendar _calendarEventListBoxHeight;
        public Calendar CalendarEventListBoxHeight
         {
             get { return _calendarEventListBoxHeight; }
             set
             {
                 if (_calendarEventListBoxHeight != value)
                 {
                     _calendarEventListBoxHeight = value;
                     OnPropertyChanged("CalendarEventListBoxHeight");
                 }
             }

         }

        private bool _showCalendar;
        public bool ShowCalendar
        {
            get { return _showCalendar; }
            set {
                if (_showCalendar != value)
                {
                    _showCalendar = value;
                    OnPropertyChanged("ShowCalendar");
                }
            }
        }

        private bool _showTasks;
        public bool ShowTasks
        {
            get { return _showTasks; }
            set
            {
                if (_showTasks != value)
                {
                    _showTasks = value;
                    OnPropertyChanged("ShowTasks");
                }
            }
        }

        private bool _showMeetings;
        public bool ShowMeetings
        {
            get { return _showMeetings; }
            set
            {
                if (_showMeetings != value)
                {
                    _showMeetings = value; OnPropertyChanged("ShowMeetings");
                }
            }
        }

        public void ShowCalendarAction()
        {
            ShowCalendar = true;
            CalendarEventListBoxHeight = Calendar.ShowCalendarMaxLength;
        }

        public void HideCalendarAction()
        {
            ShowCalendar = false;
            CalendarEventListBoxHeight = Calendar.HideCalenderHeight;
        }

        public void ShowMeetingsAction()
        {
            ShowTasks = false;
            ShowMeetings = true;
        }

        public void ShowTasksAction() {
            ShowMeetings = false;
            ShowTasks = true;
        }

        private void Init()
        {
            ShowCalendar = true;
            CalendarEventListBoxHeight = Calendar.ShowCalendarMaxLength;
            ShowMeetings = true;
            ShowTasks = false;
            ListOfMeetingsViewModel = new MeetingsListViewModel();
            ListOfTasksViewModel = new TaskListViewModel();
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

这是 XAML.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:conv="clr-namespace:WpfApplication1.Converters"
        xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
        xmlns:cal="http://www.caliburnproject.org"
        xmlns:views="clr-namespace:WpfApplication1.Views"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"   
        Title="MainWindow" Height="350" Width="525"
        >
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
        <conv:InverseBooleanConverter x:Key="InverseVisibilityConverter"/>
        <conv:GridViewLengthConverter x:Key="LengthConverter" />
    </Window.Resources>
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid Grid.Column="0" Grid.Row="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>

            <Calendar Grid.Row="0" Grid.Column="0"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      Margin="0,0,0,0"
                      Visibility="{Binding Path=ShowCalendar, Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"
                      >

            </Calendar>

        <Button Margin="0,12,0,0"  
                                   FontSize="15"
                                   Grid.Row="0"
                                   Grid.RowSpan="2"
                                   Grid.Column="1"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Top"
                                   Content="Show Calendar" 
                                   Visibility="{Binding Path=ShowCalendar,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"  
                               ToolTip="ShowCalendar">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="ShowCalendarAction" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

            </Button>
        <Button Margin="0,32,0,0" 
                                   FontSize="15"
                                   Grid.Row="0"
                                   Grid.RowSpan="2"
                                   Grid.Column="1"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Top"
                                   Visibility="{Binding Path=ShowCalendar,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"  
                                   Content="Hide Calendar" 
                                   ToolTip="HideCalendarButtonClick">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="HideCalendarAction" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

            </Button>
            <Button Margin="0,5,0,0"   
                                   Grid.Row="1"
                                   Grid.Column="0"
                                   FontSize="15" 
                                   HorizontalAlignment="Left"
                                   Visibility="{Binding Path=ShowMeetings,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"    
                                   Content="Show Meetings" 
                                   ToolTip="ShowMeetingsButtonClick">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="ShowMeetingsAction" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

            </Button>
        <Button Margin="20,5,0,0" 
                                   Grid.Row="1"
                                   Grid.Column="0"
                                   FontSize="15"
                                   Grid.ColumnSpan="3"
                                   HorizontalAlignment="Left"
                                   Visibility="{Binding Path=ShowTasks,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"  
                                   Content="Show Tasks;" 
                                   ToolTip="ShowTasksButtonClick">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="ShowTasksAction" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

            </Button>
        <Grid Grid.Row="2"
              Grid.RowSpan="3"
              Grid.Column="0"
              Grid.ColumnSpan="2"
              MaxHeight="{Binding Path=CalendarEventListBoxHeight, Mode=TwoWay, Converter={StaticResource LengthConverter }}"
              Visibility="{Binding Path=ShowMeetings, Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"
              >
                <views:MeetingsListView DataContext="{Binding Path=ListOfMeetingsViewModel,Mode=TwoWay}">

                </views:MeetingsListView>
            </Grid>
        <Grid Grid.Row="2"
              Grid.RowSpan="3"
              Grid.Column="0"
              Grid.ColumnSpan="2"
              MaxHeight="{Binding Path=CalendarEventListBoxHeight, Converter={StaticResource LengthConverter }}"
              Visibility="{Binding Path=ShowTaks,Converter={StaticResource LengthConverter}}"
              >
                        <views:TaskListView DataContext="{Binding Path=ListOfTasksViewModel,Mode=TwoWay}" />
              </Grid>

    </Grid>
    </Grid>
</Window>

为了完整起见,两个转换器:

InverseBooleanToVisibiltyConverter

public class InverseBooleanConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(Visibility))
                throw new InvalidOperationException("The target must be a boolean");

            if (!(bool)value)
            {
                return Visibility.Visible;
            }
            return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        #endregion
    } 

GridViewLengthConverter

 class GridViewLengthConverter:IValueConverter{


        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            double val = (int)value;
            GridLength gridLength = new GridLength(val);

            return gridLength;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            GridLength val = (GridLength)value;

            return val.Value;
        }
    }

我想您可以通过使用更少的布尔值优化切换行为来删除一些代码 =)...

//编辑: 我坚信问题出在您显示的代码之外。特别是在 ViewMode 的情况下,加载和交换部分或您在评论中描述为 "a lot more complex functionality" 的内容。尽管如此,因为您已经有了一个 IsCaseLoaded-属性 。我假设您正在此处进行一些异步数据提取。 Async/await 也可能对 MVVM 很棘手。特别是在将 UI 相关操作与后台操作混合时。您可以在附件中找到一些有用的链接,了解如何处理异步代码和 MVVM。本系列展示了异步可绑定通知属性、异步 IComannd 实现和异步服务的方法。

异步编程:异步 MVVM 应用程序的模式:数据绑定 https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

异步编程:异步 MVVM 应用程序的模式:命令 https://msdn.microsoft.com/en-us/magazine/dn630647.aspx

异步编程:异步 MVVM 应用程序的模式:服务 https://msdn.microsoft.com/en-us/magazine/dn683795.aspx

希望对您有所帮助...