Привязка ObservableCollection к ListView с использованием MVVM

У меня возникла проблема при попытке привязать 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>

У меня нет опыта работы с UWP, однако это похоже на WPF. Если я изменю DataTemplate для использования модели... тогда RelayCommand не изменится DataTemplate для использования Model, тогда:

Mustafa Mutasim 17.06.2024 21:38

попробуйте эту команду = "{x:Bind DeleteUserCommand, RelativeSource = {RelativeSource TemplatedParent}}"

Mustafa Mutasim 17.06.2024 21:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку ваш ListView представляет собой набор User, тип данных его ItemTemplate должен быть User.

Теперь, чтобы связать удаление Button с командой ViewModel, которая находится за пределами DataTemplate, вам понадобится небольшая хитрость.

  1. x:NamePage.
<Page x:Name = "ThisPage" ... />
  1. Переместите экземпляр UsersViewModel в код.
<!--
<Page.DataContext>
    <vm:UsersViewModel x:Name = "ViewModel"/>
</Page.DataContext>
-->
public sealed partial class UserPage: Page
{
    public UserPage()
    {
        InitializeComponent();
    }

    public UsersViewModel ViewModel { get; } = new();
}
  1. Используйте 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. Я задал по этому поводу новый вопрос.

Nuvolari 18.06.2024 04:58

Другие вопросы по теме