首页 > 解决方案 > Observable Collection 不更新用户控件内的视图

问题描述

我正在为工作学习 WPF 并在一个简单的 Todo List 应用程序上工作,并且在 Observable List 没有更新视图时遇到了一些问题。

我正在使用 MVVM Light 框架,并且已经对 CollectionChanged 事件进行了测试订阅,当我添加元素时该事件会被触发。我正在做多个视图,所以如果我不在主 UI 线程中,我添加了 MVVM Light 的 DispatcherHelper 以确保它在主线程上被调用。我还尝试通过在可观察列表上使用 MVVM Light 的 RaisePropertyChanged 函数手动重新触发 INotifyPropertyChanged(请参阅注释掉的代码)。为了确保不是我搞砸了 XAML,我还将 ListBox 更改为具有相同结果的 DataGrid。

以下是我尝试过的一些堆栈溢出消息的列表以及我为完成此操作而遵循的教程:

Todo 模型类

namespace WPFTodoList.Models
{
    class TodoItem : ObservableObject
    {
        public String Title { get { return this._title; } set
            {
                Set(() => this.Title, ref this._title, value);
            }
        }
        public String Description { get { return this._description; } set
            {
                Set(() => this.Description, ref this._description, value);
            }
        }
        public int Priority { get { return this._priority; } set
            {
                Set(() => this.Priority, ref this._priority, value);
            }
        }
        public bool Done { get { return this._done; } set
            {
                Set(() => this._done, ref this._done, value);
            }
        }


        private String _title;
        private String _description;
        private int _priority;
        private bool _done;
    }
}

查看模型类

class TodoListAppViewModel : ViewModelBase
    {

        public ObservableCollection<TodoItem> Todos { get; private set; }

        public RelayCommand AddTodoItemCommand { get{ return this._addTodoItemCommand ?? this._BuildAddTodoItemCommand(); } }

        private RelayCommand _addTodoItemCommand;
        private NavigationService _navService;
        private TodoService _todoService;
        private IDisposable _addTodoSubscription;

        public TodoListAppViewModel(NavigationService nav, TodoService todo)
        {
            this._navService = nav;
            this._todoService = todo;
            this.InitViewModel();
        }

        public TodoListAppViewModel()
        {
            this._navService = NavigationService.GetInstance();
            this._todoService = TodoService.GetInstance();
            this.InitViewModel();
        }

        private void InitViewModel()
        {
            this.Todos = new ObservableCollection<TodoItem>();
            this.Todos.CollectionChanged += this.CollectionChanged;
            this._addTodoSubscription = this._todoService.AddTodoItem.Subscribe((value) =>
            {
                /*this.Todos.Add(value);
                RaisePropertyChanged(() => this.Todos);*/
                DispatcherHelper.CheckBeginInvokeOnUI(() => this.Todos.Add(value));
            });
        }

        private RelayCommand _BuildAddTodoItemCommand()
        {
            this._addTodoItemCommand = new RelayCommand( () => this._navService.NavigateTo("addEdit"));
            return this._addTodoItemCommand;
        }

        private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            Console.WriteLine(e.Action.ToString());
        }

        ~TodoListAppViewModel()
        {
            this._addTodoSubscription.Dispose();
        }
    }
}

查看 XAML

<UserControl x:Class="WPFTodoList.Views.TodoListAppView"
             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:local="clr-namespace:WPFTodoList.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <DataTemplate x:Key="TodoItemTemplate">
            <Label Content="{Binding Title}"/>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="8*" MaxHeight="180px"/>
            <RowDefinition Height="16*"/>
        </Grid.RowDefinitions>
        <StackPanel Margin="24,-20,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Panel.ZIndex="5">
            <Label Content="Your" Foreground="White" FontSize="16" />
            <Label Content="Todo List" Foreground="White" FontSize="24" Margin="0,-15,0,0" />
        </StackPanel>
        <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5">
            <Rectangle.Fill>
                <RadialGradientBrush GradientOrigin="0.2,0.1" RadiusX="1" RadiusY="1" SpreadMethod="Reflect" MappingMode="RelativeToBoundingBox" Center="0.1,0.1">
                    <GradientStop Color="#FF5B447C" Offset="0.329"/>
                    <GradientStop Color="#FF1D1F5A" Offset="1"/>
                </RadialGradientBrush>
            </Rectangle.Fill>
        </Rectangle>
        <Rectangle Fill="#3FFFFFFF" VerticalAlignment="Bottom" Height="40" Panel.ZIndex="5"/>
        <StackPanel Panel.ZIndex="10" Height="40" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
            <Button Background="Transparent" BorderBrush="Transparent" HorizontalAlignment="Right" Command="{Binding AddTodoItemCommand}" BorderThickness="0,0,0,0" SnapsToDevicePixels="True" >
                <Button.ToolTip>
                    <Label Content="Add A Todo Item"/>
                </Button.ToolTip>
                <Image Source="../Resources/add.png" HorizontalAlignment="Right" />
            </Button>
        </StackPanel>
        <ListBox Grid.Row="1"  ItemsSource="{Binding Todos}" ItemTemplate="{DynamicResource TodoItemTemplate}"/>
    </Grid>
</UserControl>

查看代码返回

public partial class TodoListAppView : UserControl
    {
        public TodoListAppView()
        {
            InitializeComponent();
            TodoListAppViewModel vm = new TodoListAppViewModel();
            this.DataContext = vm;
        }

    }

为了在没有其他 4-6 个文件的情况下模拟待办事项,我使用 RX.net 主题将待办事项项从另一个视图中继到列表视图(如果需要,我可以发布代码)。

至于窗口如何显示这里的视图是根xaml

<Window x:Class="WPFTodoList.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFTodoList"
        mc:Ignorable="d"
        xmlns:views="clr-namespace:WPFTodoList.Views"
        xmlns:viewModels="clr-namespace:WPFTodoList.ViewModels"
        Title="MainWindow" Height="600" Width="300">
    <Window.Resources>
        <DataTemplate x:Name="AddEditPage" DataType="{x:Type viewModels:AddEditTodoViewModel}">
            <views:AddEditTodoView DataContext="{Binding}"/>
        </DataTemplate>
        <DataTemplate x:Name="TodoListApp" DataType="{x:Type viewModels:TodoListAppViewModel}">
            <views:TodoListAppView DataContext="{Binding}"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding ActivePage}"/>
    </Grid>
</Window>

ActivePage 然后绑定到 TodoListAppViewModel 的一个实例

预期的行为是当对 TodoService.AddTodoItem 订阅的订阅被触发时,传递给 lambda 的 TodoListItem 提供的实例应该添加到可观察的 Todos 列表中。该列表反过来应该更新视图。现在我看到在 TodoListAppViewModel 中触发了 CollectionChanged 事件,但是视图没有更新。

标签: c#wpfmvvm-light

解决方案


推荐阅读