【问题标题】:How to set data context of ViewModela View's xaml?如何设置 View Model Views xaml 的 datacontext?
【发布时间】:2015-06-07 01:08:48
【问题描述】:

我正在尝试将 View 的数据上下文设置为其 ViewModel 中包含的列表。但是当我测试了当前的设置时,似乎 ViewModel 和 View 之间的data context 设置不正确。

为了调试这个问题,我在视图的构造函数中设置了一个消息框,我收到以下错误消息,提示数据上下文设置不正确:“对象引用未设置为实例对象的"

该列表还在另一个 ViewModel 中使用,显示该列表不为空,这进一步暗示了数据上下文问题。

有谁知道在 View 和 ViewModel 之间设置数据上下文的缺陷是什么?

这是包含列表的 ViewModel:

namespace LC_Points.ViewModel
{
    public class ViewSubjectGradeViewModel 
    {


        public ViewSubjectGradeViewModel()
        {

            AddedSubjectGradePairs = new ObservableCollection<ScoreModel>();

        }


        public ObservableCollection<ScoreModel> AddedSubjectGradePairs { get; set; }       

    }
}

这是 View 和 View 背后的代码:

<Page x:Class="LC_Points.View.ViewSubjectGradePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:LC_Points.View"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:vms="using:LC_Points.ViewModel"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
      DataContext="{Binding ViewSubjectGradeViewModelProperty1}"
      mc:Ignorable="d">



    <Grid x:Name="LayoutRoot">

        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <EntranceThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="40*" />
            <RowDefinition Height="20*" />
            <RowDefinition Height="30*" />
            <RowDefinition Height="30*" />
            <RowDefinition Height="20*" />
            <RowDefinition Height="20*" />
        </Grid.RowDefinitions>

        <!--  Title Panel  -->
        <StackPanel Grid.Row="0" Margin="19,0,0,0">
            <TextBlock Margin="0,12,0,0"
                       Style="{ThemeResource TitleTextBlockStyle}"
                       Text="LC POINTS" />
            <TextBlock Margin="0,-6.5,0,26.5"
                       CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"
                       Foreground="DarkGreen"
                       Style="{ThemeResource HeaderTextBlockStyle}"
                       Text="View Grades" />
        </StackPanel>

        <!--  TODO: Content should be placed within the following grid  -->
        <Grid x:Name="ContentRoot"
              Grid.Row="1"
              Margin="19,9.5,19,0">

            <ListBox Height="400"
                     Margin="0,0,0,-329"
                     VerticalAlignment="Top"
                     ItemsSource="{Binding AddedSubjectGradePairs}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock>
                            <Run Text="{Binding Subject}" /><Run Text=" - " /><Run Text="{Binding Points}" />
                        </TextBlock>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

        </Grid>
    </Grid>
</Page>

查看后面的代码:

namespace LC_Points.View
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class ViewSubjectGradePage : Page
    {
        private NavigationHelper navigationHelper;
        private ObservableDictionary defaultViewModel = new ObservableDictionary();


        public ViewSubjectGradePage()
        {
            this.InitializeComponent();

            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
            this.navigationHelper.SaveState += this.NavigationHelper_SaveState;

            var messageDialog = new MessageDialog(DataContext.GetType().ToString());
            messageDialog.ShowAsync();
        }

        /// <summary>
        /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }


        /// <summary>
        /// Gets the view model for this <see cref="Page"/>.
        /// This can be changed to a strongly typed view model.
        /// </summary>
        public ObservableDictionary DefaultViewModel
        {
            get { return this.defaultViewModel; }
        }



        private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
        }


        private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
        {
        }

        #region NavigationHelper registration


        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedFrom(e);
        }

        #endregion
    }
}

【问题讨论】:

  • 尝试在OnNavigatedTo 或等效事件中显示对话框。不在构造函数上。
  • 这里是一个 -1 表示异常的快照。永远不要在问题中这样做。看看那个对话框——在底部它说“将异常详细信息复制到剪贴板”。您单击该链接,然后将其粘贴到edit。一张异常的照片比无用更糟糕。

标签: c# listview design-patterns mvvm win-universal-app


【解决方案1】:

您可能希望从模板提供的您未使用的视图代码中删除任何生成的代码。这可能会导致混淆,因为模板希望您使用本地 ObservableCollection 作为您的 DataContext。

有 3 种主要方法可以将 ViewModel 设置为 View 的 DataContext。

  1. 使用View后面的代码:

    public ViewSubjectGradePage()
    {
        this.InitializeComponent();
        this.DataContext = new ViewSubjectGradeViewModel();
    }
    
  2. 使用 XAML(为了便于阅读,删除了其他页面属性):

    <Page x:Class="LC_Points.View.ViewSubjectGradePage"
        xmlns:vms="using:LC_Points.ViewModel">
        <Page.DataContext>
            <vms:ViewSubjectGradeViewModel/>
        </Page.DataContext>
    </Page>
    
  3. 使用像 Prism 这样的 MVVM 框架。这将根据标准命名约定自动连接您的 View 和 ViewModel。

我更喜欢选项 3,因为它可以提供更松散耦合的系统,但对于一个非常小的项目来说可能有点矫枉过正,而且任何框架都有一个学习曲线,但好处是很大的。选项 2 是我的第二次投票,因为它使代码保持清洁。选项 1 是我不再做的事情,但它是一种很好的快速方法。

【讨论】:

  • 我已经尝试了您在上面发布的代码和 Xaml 解决方案,但是对于每种方法,列表的内容都不会显示在视图的列表视图上,只是空白。这是第一种方法hastebin.com/tadakeduko.cs 和第二种方法hastebin.com/gazeyuseku.xml 任何想法可能是什么问题或如何进一步调试?谢谢
  • 其中任何一个的代码看起来都不错。您可以发布 ViewModel 代码吗?此外,当您运行调试器时,请检查输出窗口。它将输出任何绑定错误。它可以将您指向一个错误命名的属性,并且非常有帮助。
  • 查看问题中的示例代码,您的 ViewModel 没有实现 INotifyPropertyChanged,因此当您的构造函数设置您的可观察集合时,视图不会收到通知。我喜欢为集合设置私有只读字段,并让实际属性返回该字段。这种方式绑定总是有效的。如果您像构造函数那样更改整个集合,则必须发出 PropertyChanged 事件,以便视图知道要更新。
  • 所以我尝试了上面的建议,输出窗口中没有绑定错误。这是我更新后的 View、VM 和 View 代码,gist.github.com/BrianJVarley/842e56610473c9217acc 仍然没有变化。任何想法我可以如何进一步调试,不应该是我想的那么复杂。这是github上的repo,如果你有时间可以自己测试github.com/BrianJVarley/LC_Points
  • 我为您创建了一个显示问题的拉取请求。在这里总结一下,需要一个通用的数据存储。一个新的 ViewModel 被正确初始化并且所有的绑定都是正确的,它只是每次都创建一个新的集合,而不是引用一个公共的数据存储。一个 ViewModel 将数据存储到一个局部变量中,因此下一个 ViewModel 对这些数据一无所知。
【解决方案2】:

我认为这是由于您分配 DataContext 的方式所致。在您的 XAML 中,您将 DataContext 绑定到一个属性。我在您的代码中没有看到您实际将数据上下文分配给defaultViewModel 或其中的属性的任何地方。

尝试将您的构造函数更新为以下内容。

    public ViewSubjectGradePage()
    {
        this.InitializeComponent();

        this.navigationHelper = new NavigationHelper(this);
        this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
        this.navigationHelper.SaveState += this.NavigationHelper_SaveState;

        this.DataContext = this.DefaultViewModel;
    }

如果您想将其分配给DefaultViewModel 中的属性,您也可以在那里这样做。然后删除 XAML 中的 DataContext 分配。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-06
    • 2014-07-06
    • 1970-01-01
    • 2011-12-04
    • 1970-01-01
    • 2013-09-29
    相关资源
    最近更新 更多