【问题标题】:What happens to previous DataContexts in Visual Tree when DataContext on same View is changed?当同一视图上的 DataContext 更改时,Visual Tree 中的先前 DataContext 会发生什么情况?
【发布时间】:2016-02-09 08:53:56
【问题描述】:

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

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

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 的构造函数之后一切看起来都很好,但是在单击一个按钮后它突然具有不同的值。

我还认为我的一些线程正在更改某些内容,但我尝试对其进行调试,但在此期间没有一个线程(仅主线程)处于活动状态。

【问题讨论】:

  • 这是一个棘手的问题。因为代码中存在很多潜在的副作用,此处未显示。你能显示更多代码吗? TasksViewModel、MeetingsViewModel、CalendarViewModel?
  • EventCalendarViewModel here ... 任务和会议没有它们的视图模型,您还想要 EventCalendar.xaml 吗?看看我如何绑定 ShowMeetingsButtonClick?
  • EventCalendar.xaml ... 代码很多,所以不知道你能不能找到你想要的,明白我说的一些东西是什么意思
  • 我也尝试了一些编辑(没有成功) - 例如添加 this.Refresh() 和很多 NotifyOfPropertyChange 东西
  • 在设置新数据之前清空数据上下文。像这样:((EventCalendar)source).DataContext = null; ((EventCalendar)source).DataContext = new EventCalendarViewModel...

标签: c# wpf mvvm datacontext


【解决方案1】:

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

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

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

我以某种方式对您的属性和 PropertyNotification-Stuff 进行了排序。目标是尽可能保持紧实和精简。所以对于这段代码,我只能从属性本身调用 OnPropertyChanged。

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

同样的事情也适用于日历中的实际事件和亮点内容。它应该在一个自己的 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。尽管如此,因为您已经有了一个 IsCaseLoaded-Property。我假设您正在这里进行一些异步数据获取。 MVVM 的异步/等待也可能很棘手。尤其是在将 UI 相关操作与后台操作混合时。附上你会发现一些有用的链接如何处理异步代码和 MVVM。本系列展示了 async-bindable-notification-properties、异步 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

希望对您有所帮助...

【讨论】:

  • 嗨,我现在要试试。听起来真的很合理。我今天来上班,我们想到的第一件事是尝试更改转换器,也许使用 Enum,就像你在这里写的那样......我会试一试。感谢您的帮助和有用的答案
  • 如果您的问题在更改后仍然存在,请告诉我,我们可以进一步详细说明...
  • 是的,问题仍然存在。我发现在加载案例(将新的 ViewModel 添加到 DataContext)几次(主要是 3 次)之后,日历仍然以某种方式连接到以前的 ViewModel。即使构造函数正常工作并且在 UI 中它被重新加载(我看到默认数据)。但是在我单击一个按钮后,它正在与以前的 ViewModel 一起使用。怎么可能,它可以找到前一个?它现在不应该有任何联系。那个时候应该被垃圾收集器删除,还是我错了?
  • 您能否分享一些有关如何加载案例和刷新等的更多详细信息。所以我认为问题在于...
  • EventCalendar 这是我的视图,我等到IsCaseLoaded 更改,然后在LoadPCCallback 中分配新的DataContext。而IsCaseLoaded 绑定在另一个 ViewModel (CaseViewModel) 中。加载案例时,它是true。这似乎有效,因为每次加载时都会调用LoadPCCallback
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-08
  • 1970-01-01
  • 2018-11-14
相关资源
最近更新 更多