![](/img/trans.png)
[英]WPF Combobox in view not updating when bound property in viewmodel updated
[英]Updating a Bound Property does not update the View in WPF
我正在玩 MVVM 模式。
我构建了一个简单的应用程序,其中仅包含
- 显示 UserControl 的简单集合的 ListView,
- 将新项目添加到 ListView 的按钮和
- 一个允许用户编辑 SelectedItem 的 ContextMenu。
我有两个 ICommand 绑定到我的视图。 ItemBeginEdit
和ItemEndEdit
。
它们都在整个应用程序中正确调用,并且参数正确传递。 经测试。
每个命令的 CommandParameter 是 ListView.SelectedItem (UserControl) 的绑定源。
说清楚:
- ListView.ItemSource
= Models.Item
的集合
- ListView.DataTemplate
= UserControls.Item
- UserControls.Item.Source
= Models.Item
类型的DependencyProperty
。
更改整个Collection
时, ListView
会正确显示我的所有项目。
当我从ListView.SelectedItem
获取Models.Item
的reference
并在 ICommand 执行中更改其属性之一时,它不会在 UI 上更新。
我注意到,如果我在ViewModel
的Constructor
中编辑 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.