У меня возникла проблема при попытке привязать Observable Collection к элементу управления ListView. Я не уверен, что мой шаблон проектирования верен. Я храню список пользователей в модели представления как ObservableCollection. Затем я устанавливаю источник ListView в качестве этой коллекции. Однако когда я делаю ListView.ItemTemplate, ему не нравится следующее:
<TextBlock Text = "{x:Bind Username}" Grid.Column = "0"></TextBlock>"
Пишет, что имя пользователя не найдено в UserViewModel.
Если я изменю DataTemplate на использование модели, идентификатор/имя пользователя будет работать, но RelayCommand — нет.
Я использую WinUi 3, Entity Framework Core и набор инструментов сообщества MVVM.
Это код, который у меня есть до сих пор:
Модель
public partial class User: ObservableObject
{
public int Id { get; set; }
[ObservableProperty]
private string _username = string.Empty;
partial void OnUsernameChanged(string? oldValue, string newValue)
{
Debug.WriteLine("Username changed");
}
}
Посмотреть модель
public partial class UserViewModel : ObservableObject
{
RecipeDBContext context;
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
if (_users != value)
{
if (_users != null)
{
_users.CollectionChanged -= OnCollectionChanged;
}
_users = value;
if (_users != null)
{
_users.CollectionChanged += OnCollectionChanged;
}
}
}
}
public UserViewModel()
{
context = new RecipeDBContext();
_users = new ObservableCollection<User>();
UpdateUsers(context.User.ToList());
}
public void UpdateUsers(List<User> users)
{
_users.Clear();
foreach (User user in users)
{
Users.Add(user);
}
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (User newItem in e.NewItems)
{
context.User.Add(newItem);
}
}
if (e.OldItems != null)
{
foreach (User oldItem in e.OldItems)
{
context.User.Remove(oldItem);
}
}
context.SaveChanges();
}
[RelayCommand]
public void DeleteUser(object o)
{
Debug.WriteLine("Called Delete User");
if (o != null)
{
int id = int.Parse(o.ToString());
using (context)
{
try
{
context.User.Remove(new User() { Id = id });
context.SaveChanges();
}
catch (Exception ex)
{
if (!context.User.Any(i => i.Id == id))
{
return;
}
else
{
throw ex;
}
}
}
}
}
[RelayCommand]
public void AddUser(object o)
{
if (o != null)
{
string username=o as string;
if (username != String.Empty)
{
using (context)
{
var user = new User()
{
Username = username,
};
context.User.Add(user);
context.SaveChanges();
}
}
}
}
[RelayCommand]
private void ModifyName(object o)
{
string oldName = "fred";
if (o != null)
{
string newUsername = o as string;
if (newUsername != String.Empty)
{
for (int i = Users.Count - 1; i >= 0; i--)
{
if (Users[i].Username == oldName)
{
Users[i].Username = newUsername;
context.SaveChanges();
}
}
}
else
{
}
}
}
}
Вид
<Page
x:Class = "Test.Views.UserPage"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local = "using:Test.Views"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm = "using:Test.ViewModels"
mc:Ignorable = "d"
xmlns:m = "using:Test.Models">
<Page.DataContext>
<vm:UserViewModel x:Name = "ViewModel"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "0.2*"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
<RowDefinition Height = "Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border BorderThickness = "1" BorderBrush = "Black" />
<TextBlock FontSize = "30" Grid.Row = "0" Grid.Column = "0">LOGIN</TextBlock>
<TextBlock Grid.Row = "1" Grid.Column = "0">User</TextBlock>
<ListView x:Name = "usersListView" Grid.Row = "1" Grid.Column = "1" ItemsSource = "{x:Bind ViewModel.Users, Mode=TwoWay}">
<ListView.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListViewHeaderItem Grid.Column = "0">
<TextBlock>Username</TextBlock>
</ListViewHeaderItem>
<ListViewHeaderItem Grid.Column = "1">
<TextBlock>Recipes</TextBlock>
</ListViewHeaderItem>
<ListViewHeaderItem Grid.Column = "2">
<TextBlock>Delete</TextBlock>
</ListViewHeaderItem>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate x:DataType = "vm:UserViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
<ColumnDefinition Width = "0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text = "{x:Bind Username}" Grid.Column = "0"></TextBlock>
<Button Grid.Column = "2" Command = "{x:Bind DeleteUserCommand}" CommandParameter = "{x:Bind Id}">X</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name = "loginUserBtn" Click = "loginUserButton_Click" Grid.Row = "2" Grid.Column = "1">Login</Button>
<TextBlock Grid.Row = "3" Grid.Column = "0" FontSize = "30">New User</TextBlock>
<TextBlock Grid.Row = "4" Grid.Column = "0">Name</TextBlock>
<TextBox x:Name = "newUserName" Grid.Row = "4" Grid.Column = "1"></TextBox>
<Button x:Name = "addUserBtn" Click = "addUserButton_Click" Grid.Row = "5" Grid.Column = "1">Add User</Button>
<Button x:Name = "addUserBtn1" Command = "{x:Bind ViewModel.AddUserCommand}" CommandParameter = "{Binding Text, ElementName=newUserName}" Grid.Row = "5" Grid.Column = "2">Add User1</Button>
<Button x:Name = "getUserBtn" Click = "getUserButton_Click" Grid.Row = "6" Grid.Column = "0">Get All</Button>
</Grid>
</Page>
попробуйте эту команду = "{x:Bind DeleteUserCommand, RelativeSource = {RelativeSource TemplatedParent}}"





Поскольку ваш ListView представляет собой набор User, тип данных его ItemTemplate должен быть User.
Теперь, чтобы связать удаление Button с командой ViewModel, которая находится за пределами DataTemplate, вам понадобится небольшая хитрость.
x:NamePage.<Page x:Name = "ThisPage" ... />
UsersViewModel в код.<!--
<Page.DataContext>
<vm:UsersViewModel x:Name = "ViewModel"/>
</Page.DataContext>
-->
public sealed partial class UserPage: Page
{
public UserPage()
{
InitializeComponent();
}
public UsersViewModel ViewModel { get; } = new();
}
Binding с ElementName и Path, чтобы связать команду.<ListView ItemsSource = "{x:Bind ViewModel.Users}">
<ListView.ItemTemplate>
<DataTemplate x:DataType = "local:User">
<Grid ColumnDefinitions = "*,Auto">
<TextBlock
Grid.Column = "0"
Text = "{x:Bind Username}" />
<Button
Grid.Column = "1"
Command = "{Binding ElementName=ThisPage, Path=ViewModel.DeleteUserCommand}"
CommandParameter = "{x:Bind}"
Content = "Delete" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class User : ObservableObject
{
[ObservableProperty]
private string _username = string.Empty;
}
public partial class UsersViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<User> _users =
[
new User { Username = "User1" },
new User { Username = "User2" },
new User { Username = "User3" },
];
[RelayCommand]
private void DeleteUser(User user)
{
Users.Remove(user);
}
}
Еще раз спасибо Андрею, он сделал свою работу. Однако я получаю исключение при удалении/добавлении в Observable Collection. Я задал по этому поводу новый вопрос.
У меня нет опыта работы с
UWP, однако это похоже наWPF. Если я изменю DataTemplate для использования модели... тогда RelayCommand не изменитсяDataTemplateдля использованияModel, тогда: