【问题标题】:MVVM view only updating when data set in viewmodel's constructorMVVM 视图仅在视图模型的构造函数中设置数据时更新
【发布时间】:2018-09-25 22:40:50
【问题描述】:

我假设这可能很容易,但我被卡住了。我有一个组合框,我可以在 viewmodel 的构造函数中更新其数据,但是如果在 viewmodel 初始化后设置数据,我将无法更新视图。我认为这要么与我的 DataContext 或我的事件广播有关,但我要么遗漏了某些东西,要么不理解某些东西。

此外,我收到与我的“DataPane”属性相关的绑定路径错误,说在 RedViewModel 中找不到它。如果我在 RedViewModel 中放置 DataPane 的属性定义,控制台将停止抱怨,但代码在这两种情况下都有效。 DataPane 是我的 MainView 的 DataContext,而不是我的 RedView,所以我不确定为什么会这样。这可能是也可能不是相关的背景信息;我最感兴趣的是这个问题的第一部分。

到目前为止,这是我的 XAML:

<UserControl x:Class="TableBuilder.Views.RedView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:views="clr-namespace:TableBuilder.Views"
         xmlns:viewmodels="clr-namespace:TableBuilder.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
    <viewmodels:RedViewModel />
</UserControl.DataContext>
<StackPanel Background="Red">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
        </Grid.ColumnDefinitions>

        <TextBlock Text="Type:" Grid.Column="1" />
        <TextBlock Text="Enumeration" Grid.Column="3" />
    </Grid>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
        </Grid.ColumnDefinitions>

        <TextBlock Text="Value:" Grid.Column="1" />
        <ComboBox Grid.Column="3" ItemsSource="{Binding EnumValueItems}" VerticalAlignment="Top">
        </ComboBox>
    </Grid>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
        </Grid.ColumnDefinitions>

        <TextBlock Text="Units:" Grid.Column="1" />
        <TextBlock Text="Integer" Grid.Column="3" />
    </Grid>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
        </Grid.ColumnDefinitions>

        <TextBlock Text="Description:" Grid.Column="1" />
        <TextBlock Text="text" Grid.Column="3" />
    </Grid>
</StackPanel>

这是视图模型:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Data;
using TableBuilder.Helpers;
using TableBuilder.Models;

namespace TableBuilder.ViewModels
{
    public class RedViewModel : ViewModelBase
    {
        private object _dataPane;
        public object DataPane
        {
            get { return _dataPane; }
            private set
            {
                _dataPane = value;
                OnPropertyChanged(nameof(DataPane));
            }
        }
        private ListCollectionView _enumValueItems;
        public ListCollectionView EnumValueItems
        {
            get { return _enumValueItems;  }
            set
            {
                _enumValueItems = value;
                OnPropertyChanged(nameof(EnumValueItems));
            }
        }
        public bool Loaded { get; set; }

        private ObservableCollection<string> _listData;

        public ObservableCollection<string> ListData
        {
            get { return _listData; }
            set
            {
                _listData = value;
                EnumValueItems = new ListCollectionView(ListData);
                //EnumValueItems.MoveCurrentTo(ListData[0]);
                //OnPropertyChanged("ListData");
            }
        } 

        public RedViewModel()
        {
            //_listData = new ObservableCollection<string>();
            //EnumValueItems = new ListCollectionView(_listData);
            //EnumValueItems.CurrentChanged += enumValueItems_CurrentChanged;
            //GetListData();
        }
        public void GetListData()
        {
            ListData = new ObservableCollection<string>();
            RedModel model = new RedModel();
            ListData = model.GetListData();
            //EnumValueItems = new ListCollectionView(model.GetListData());
            //EnumValueItems.MoveCurrentTo(ListData[0]);
            //EnumValueItems.CurrentChanged += EnumValueItems_CurrentChanged;
        }

        private void EnumValueItems_CurrentChanged(object sender, EventArgs e)
        {
            if (Loaded)
            {
                MessageBox.Show((((ListCollectionView)sender).CurrentItem).ToString());
            }
            Loaded = true;
        }
    }
}

我的 ViewModelBase 类在这里:

using System.ComponentModel;
using TableBuilder.Models;
using TableBuilder.ViewModels;

namespace TableBuilder.Helpers
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

编辑: 这是我用来尝试修改列表数据的测试代码:

        public MainViewModel()
    {
        OpenCommand = new RelayCommand(ExecuteOpen);
        ExitCommand = new RelayCommand(ExecuteExit);
        var vm = new RedViewModel();
        var test = new ObservableCollection<string>();
        test.Add("Test1");
        test.Add("Test2");
        test.Add("Test3");
        test.Add("Test4");
        test.Add("Test5");
        test.Add("Test6");
        test.Add("Test7");
        vm.EnumValueItems.Clear();
        foreach (string s in test) { vm.EnumValueItems.Add(s); }
        DataPane = vm;
    }

我想我可能会以某种方式破坏这里的数据绑定。

编辑 2:

为了让问题更加清晰和清晰,这里是这个程序的总体架构(无论是好是坏):

这个程序是一个专门的 XML 解析器,用于为基于 XML 定义的嵌入式软件创建配置表。屏幕右侧根据用户选择的树节点显示 XML 数据的相关部分。左侧将显示各种视图,等待用户是否选择了 enum/float32/struct/etc 的变量。我现在感兴趣的代码是 Enum 视图,因此有一个下拉菜单,可以根据 XML 定义填充可能的枚举值。

我要做的是从 MainViewModel 填充 RedViewModel 中的下拉菜单(一旦我让一切按预期工作,这将成为我的枚举查看器)。如果我在 RedViewModel 的构造函数中填充列表,它似乎可以正确显示。但是,如果我在其他任何地方填充它(在我提供的示例中,我在实例化 RedViewModel 后将它填充到 MainViewModel 的构造函数中),它似乎不会更新。

【问题讨论】:

  • “在视图模型初始化后”设置了哪些数据,在代码中的什么位置设置?
  • 我已经更新了原始帖子以包含我的测试代码。
  • 您已经发布了视图模型的构造函数...?那么“在视图模型初始化之后”是什么意思?你在哪里实例化 MainViewModel 以及你在哪里绑定到 DataPane?请澄清您的问题并提供您的问题的最小示例。
  • 我更新了原来的问题。简而言之,有多个视图模型。如果我在 RedViewModel 的构造函数中更新数据,它工作正常。在上面的例子中,我尝试实例化 RedViewModel 然后设置它的数据。我在另一个视图模型的构造函数中测试它,因为很容易看出它是否在那里工作。 RedView 确实显示正确,但以这种方式创建时,我的下拉菜单的内容是空的。

标签: c# wpf mvvm


【解决方案1】:

EnumValueItems 分配一个新值将破坏绑定。相反,您应该绑定到ObservableCollection。然后清除项目并在更改时从其他集合中添加它们。

代替:

EnumValueItems = NewCollectionView

试试这个(改成ObservableCollection后):

EnumValueItems.Clear();
foreach (ObjectType ob in NewCollection) {EnumValueItems.Add(ob);}

【讨论】:

  • 我完全同意。
  • 好收获。尽管您的解决方案确实有意义,但它似乎并没有解决我的问题。我已经更新了原始帖子以包含我用来尝试调用它的代码;也许那里有什么不正确的地方。
【解决方案2】:

EnumValueItems 的绑定仅在列表的引用更改时通知更改,而不是在列表中添加或删除项目时。 p>

为了与当前系统保持一致,取消分配对新列表的引用,由于INotifyPropertyChanged,控件将更改为新列表。

【讨论】:

  • 有趣。我不知道这个怪癖。为了确保我正确理解您,我想创建一个新的 EnumValueItems 吗? (与之前的答案相反)
  • 是的,创建一个临时列表,填写它,然后将列表分配给EnumValueItems。我已经这样做了/这样做是为了更新控件。现在,如果这种情况经常发生,您可能需要查看ObservableCollection,它会在列表中发生添加或删除操作时发出通知。
  • 我想我误解了您的原始评论,因为我已将代码更改为使用 ObservableCollection 而不是 ListCollectionView。似乎仍然没有更新列表。我有点认为这将是一个比现在更容易的问题,而且我已经用尽了尽可能多的教程和参考资料。调试这样的东西有什么技巧吗?它正在发送更新,但 UI 没有响应。
  • 只需使用List&lt;string&gt; 代替。保持绑定并按照我的建议进行更改;尝试。 View 已关闭。
  • 查看我的博客 MVVM 绑定策略Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding 看看是否有帮助。
猜你喜欢
  • 1970-01-01
  • 2018-06-30
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-05
  • 1970-01-01
  • 2011-05-01
相关资源
最近更新 更多