I am playing around with the MVVM pattern.
I have built a simple application that includes just
-a ListView that displays a simple collection of a UserControl,
-a Button that adds new items to the ListView and
-a ContextMenu that should allow the user to edit the SelectedItem.
I have two ICommands bound to my View. ItemBeginEdit
and ItemEndEdit
.
They are both called properly throughout the app and the parameters are passed through correctly. Tested.
The CommandParameter for each Command is the Bound source of the ListView.SelectedItem (UserControl).
To make it clear:
- ListView.ItemSource
= Collection of Models.Item
- ListView.DataTemplate
= UserControls.Item
- UserControls.Item.Source
= DependencyProperty
of type Models.Item
.
When changing the whole Collection
, the ListView
displays correctly all of my items.
When I am getting a reference
of Models.Item
from the ListView.SelectedItem
and change one of its properties in the ICommand execution, it is not updated on the UI.
I have noticed that if I edit the Collection in the Constructor
of my ViewModel
, the UI updates just for that time only.
If I try to changed it back through the ICommand
, nothing happens.
INotifyPropertyChanged
is implemented to the relevant classes.
I am still learning WPF and XAML. What do I miss?
- MainWindow.xaml (View)
<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 (ViewModel)
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 (Model)
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 (UserControl)
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();
}
}
}
It seems that you are assuming that the DataSource
property of the Item
control is set whenever a property of the referenced CustomItem
class changes. That is not the case.
You should remove the DataSource
property and add properties that are bound to the view model item properties like this:
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<uc:Item Text="{Binding Name}" ... />
...
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
The elements in the UserControl's XAML will bind to its properties like
<TextBlock Text="{Binding Text,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
An alternative to exposing a set of UserControl properties would be that the elements in the UserControl's XAML bind directly to the view model properties, like
<TextBlock Text="{Binding Name}" ... />
Then ItemTemplate would then simply contain this:
<uc:Item />
This would however make the UserControl dependent on the view model type (or more precisely on any type that exposes an equivalent set of properties).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.