繁体   English   中英

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

[英]Updating a Bound Property does not update the View in WPF

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


问题:

我有两个 ICommand 绑定到我的视图。 ItemBeginEditItemEndEdit
它们都在整个应用程序中正确调用,并且参数正确传递。 经测试。
每个命令的 CommandParameter 是 ListView.SelectedItem (UserControl) 的绑定源。
说清楚:
- ListView.ItemSource = Models.Item的集合
- ListView.DataTemplate = UserControls.Item
- UserControls.Item.Source = Models.Item类型的DependencyProperty
更改整个Collection时, ListView会正确显示我的所有项目。
当我从ListView.SelectedItem获取Models.Itemreference并在 ICommand 执行中更改其属性之一时,它不会在 UI 上更新。
我注意到,如果我在ViewModelConstructor中编辑 Collection,UI 只会在那个时候更新。
如果我尝试通过ICommand将其改回,则不会发生任何事情。
INotifyPropertyChanged实现到相关类。
我还在学习 WPF 和 XAML。 我想念什么?


编码:

- MainWindow.xaml (查看)

<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>


- MainVM.cs (视图模型)

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);
        }
    }
}

- CustomItem.cs (模型)

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();
                }
            }
        }
    }
}

- CmdItemBeginEdit.cs (ICommand)

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;
        }
    }
}

- CmdItemEndEdit.cs (ICommand)

using System;
using System.Windows.Input;

namespace Demo.ViewModels.Commands
{
    public class CmdItemEndEdit : 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 = false;
        }
        #endregion

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

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

- Item.xaml (UserControl)

<UserControl x:Class="Demo.Views.UserControls.Item"
         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:Demo.Views.UserControls"
         mc:Ignorable="d"
         d:DesignHeight="45"
         d:DesignWidth="90">

    <StackPanel HorizontalAlignment="Left"
                VerticalAlignment="Center">
        <TextBlock x:Name="tblockName"
                   Text="Name"
                   Margin="2"
                   FontSize="15"
                   VerticalAlignment="Center" />
        <TextBox x:Name="tboxInput"
                 Text="Name"
                 Visibility="Collapsed"
                 Margin="2"
                 FontSize="14"
                 FontStyle="Italic"
                 VerticalAlignment="Center">
        </TextBox>
    </StackPanel>
</UserControl>

- Item.xaml.cs (用户控制)

using System;
using System.Windows;
using System.Windows.Controls;

namespace Demo.Views.UserControls
{
    public partial class Item : UserControl
    {
        #region DependencyProperty - DataSource
        public Models.CustomItem DataSource
        {
            get { return (Models.CustomItem)GetValue(DataSourceProperty); }
            set { SetValue(DataSourceProperty, value); }
        }
        public static readonly DependencyProperty DataSourceProperty =
            DependencyProperty.Register("DataSource", typeof(Models.CustomItem), typeof(Item), new PropertyMetadata(null, SetDataSource));
        private static void SetDataSource(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as Item;
            var value = e.NewValue as Models.CustomItem;

            if (value == null)
            {
                control.tblockName.Text = string.Empty;
                control.IsEditing = false;
            }
            else
            {
                control.tblockName.Text = value.Name;
                control.IsEditing = value.IsEditing;
            }
        }
        #endregion


        #region DependencyProperty - IsEditing
        public bool IsEditing
        {
            get { return (bool)GetValue(IsEditingProperty); }
            set { SetValue(IsEditingProperty, value); }
        }
        public static readonly DependencyProperty IsEditingProperty =
            DependencyProperty.Register("IsEditing", typeof(bool), typeof(Item), new PropertyMetadata(false, SetIsEditing));
        private static void SetIsEditing(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as Item;
            var value = (bool)(e.NewValue);

            if (value)
            {
                control.tblockName.Visibility = Visibility.Collapsed;
                control.tboxInput.Visibility = Visibility.Visible;
            }
            else
            {
                control.tboxInput.Visibility = Visibility.Collapsed;
                control.tblockName.Visibility = Visibility.Visible;
            }
        }
        #endregion

        //Constructors 
        public Item()
        {
            InitializeComponent();
        }
    }
}

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

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

<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 中的元素直接绑定到视图 model 属性,例如

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

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

<uc:Item />

然而,这将使 UserControl 依赖于视图 model 类型(或更准确地说,依赖于公开一组等效属性的任何类型)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM