更新绑定属性不会更新WPF中的视图

时间:2020-06-02 14:30:10

标签: c# wpf xaml mvvm data-binding

我正在玩MVVM模式。
我建立了一个简单的应用程序,其中仅包含
-一个ListView,显示UserControl的简单集合,
-一个将新项目添加到ListView和
的按钮 -a ContextMenu,该菜单应允许用户编辑SelectedItem。


问题:

我有两个绑定到我的View的ICommands。 ItemBeginEditItemEndEdit
在整个应用程序中都可以正确调用它们,并且可以正确传递参数。经过测试。
每个命令的CommandParameter是ListView.SelectedItem(UserControl)的绑定源。
明确说明:
-ListView.ItemSource = Models.Item的集合
-ListView.DataTemplate = UserControls.Item
-UserControls.Item.Source =类型DependencyProperty的{​​{1}}。
更改整个Models.Item时,Collection正确显示我的所有物品。
当我从ListView获得reference的{​​{1}}并在ICommand执行中更改其属性之一时,它不会在UI上更新。
我注意到,如果我在Models.Item的{​​{1}}中编辑集合,则UI仅在该时间更新。
如果我尝试通过ListView.SelectedItem将其改回,则什么也不会发生。
Constructor已实现到相关类。
我仍在学习WPF和XAML。我想念什么?


代码:

- MainWindow.xaml (查看)

ViewModel


- MainVM.cs (ViewModel)

ICommand

- CustomItem.cs (模型)

INotifyPropertyChanged

- CmdItemBeginEdit.cs (ICommand)

<Window x:Class="Demo.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:Demo"
    xmlns:uc="clr-namespace:Demo.Views.UserControls"
    xmlns:vm="clr-namespace:Demo.ViewModels"
    xmlns:b="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    mc:Ignorable="d"
    x:Name="windowMain"
    Title="Demo"
    Height="300"
    Width="300"
    WindowStartupLocation="CenterScreen">

<Window.DataContext>
    <vm:MainVM />
</Window.DataContext>

<StackPanel>
    <Button x:Name="btnAddItem"
            Content="Add"
            Width="150"
            Command="{Binding ItemNew}" />       
    <ListView x:Name="lviewCollection"
              Width="150"
              MinHeight="100"
              HorizontalContentAlignment="Stretch"
              ItemsSource="{Binding Items}"
              SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <b:Interaction.Triggers>
            <b:EventTrigger EventName="LostFocus">
                <b:InvokeCommandAction Command="{Binding ItemEndEdit}"
                                       CommandParameter="{Binding SelectedItem}" />
            </b:EventTrigger>
        </b:Interaction.Triggers>
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <uc:Item DataSource="{Binding}" 
                             IsEditing="{Binding IsEditing}">
                    </uc:Item>

                    <StackPanel.ContextMenu>
                        <ContextMenu DataContext="{Binding Source={x:Reference Name=windowMain}, Path=DataContext}">
                            <MenuItem Header="Rename"
                                      Command="{Binding ItemBeginEdit}"
                                      CommandParameter="{Binding SelectedItem}" />
                        </ContextMenu>
                    </StackPanel.ContextMenu>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackPanel>

- CmdItemEndEdit.cs (ICommand)

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Demo.ViewModels
{
    public class MainVM : INotifyPropertyChanged
    {
        //INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string PropertyName = "")
        {
            var args = new PropertyChangedEventArgs(PropertyName);
            PropertyChanged?.Invoke(this, args);
        }

        //Properties 
        public ObservableCollection<Models.CustomItem> Items { get; set; }

        private Models.CustomItem selectedItem;
        public Models.CustomItem SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (value == null)
                    return;
                if (value == selectedItem)
                    return;

                selectedItem = value;
                OnPropertyChanged();
            }
        }

        public Commands.CmdItemNew ItemNew { get; set; }
        public Commands.CmdItemBeginEdit ItemBeginEdit { get; set; }
        public Commands.CmdItemEndEdit ItemEndEdit { get; set; }

        //Constructors 
        public MainVM()
        {
            Items = new ObservableCollection<Models.CustomItem>();
            SelectedItem = null;
            ItemNew = new Commands.CmdItemNew(this);
            ItemBeginEdit = new Commands.CmdItemBeginEdit(this);
            ItemEndEdit = new Commands.CmdItemEndEdit(this);

            ReadItems();
        }

        //Methods
        public void SetupItems()
        {
            //Just create demo items. Not used. 
            var items = DatabaseHelper.ReadItems();
            int itemsToCreate = 3 - items.Length;
            while (itemsToCreate > 0)
            {
                CreateItem();
                itemsToCreate--;
            }
        }

        public void CreateItem()
        {
            var item = new Models.CustomItem()
            {
                Name = "New Item"
            };
            DatabaseHelper.Insert(item);
        }

        public void ReadItems()
        {
            var items = DatabaseHelper.ReadItems();
            Items.Clear();
            foreach (var item in items)
                Items.Add(item);
        }
    }
}

- Item.xaml (UserControl)

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using SQLite;

namespace Demo.Models
{
    public class CustomItem : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string PropertyName = "")
        {
            if (PropertyChanged != null)
            {
                var args = new PropertyChangedEventArgs(PropertyName);
                PropertyChanged.Invoke(this, args);
            }
        }
        #endregion

        //Properties 
        private int id;
        [PrimaryKey, AutoIncrement]
        public int Id
        {
            get { return id; }
            set
            {
                if (value != id)
                {
                    id = value;
                    OnPropertyChanged();
                }
            }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                if (value != name)
                {
                    name = value;
                    OnPropertyChanged();
                }
            }
        }

        private bool isEditing;
        [Ignore]
        public bool IsEditing
        {
            get
            {
                return isEditing;
            }
            set
            {
                if (value != isEditing)
                {
                    isEditing = value;
                    OnPropertyChanged();
                }
            }
        }
    }
}

- Item.xaml.cs (UserControl)

using System;
using System.Windows.Input;

namespace Demo.ViewModels.Commands
{
    public class CmdItemBeginEdit : ICommand
    {
        #region ICommand implementation 
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public void Execute(object parameter)
        {
            var item = parameter as Models.CustomItem;
            if (item == null)
                return;

            item.IsEditing = true;
        }
        #endregion

        //Properties
        public MainVM VM { get; set; }

        //Constructors 
        public CmdItemBeginEdit(MainVM VM)
        {
            this.VM = VM;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

似乎您假设只要所引用的DataSource类的属性发生更改,Item控件的CustomItem属性就会被设置。事实并非如此。

您应该删除DataSource属性,并添加绑定到视图模型项属性的属性,如下所示:

<ListView.ItemTemplate>
    <DataTemplate>
        <StackPanel>
            <uc:Item Text="{Binding Name}" ... />
            ...
        </StackPanel>
    </DataTemplate>
</ListView.ItemTemplate>

UserControl的XAML中的元素将绑定到其属性,例如

<TextBlock Text="{Binding Text,
                  RelativeSource={RelativeSource AncestorType=UserControl}}"/>

暴露一组UserControl属性的另一种方法是,将UserControl的XAML中的元素直接绑定到视图模型属性,例如

<TextBlock Text="{Binding Name}" ... />

然后ItemTemplate将仅包含以下内容:

<uc:Item />

但是,这会使UserControl依赖于视图模型类型(或更确切地说,依赖于公开等效属性集的任何类型)。