0

реализую проект MWWM паттерном, CRUD элементов осуществляется через команды (Lambda(Relay)Command). Не удаляется из View CategorySearchWord после удаления.

Вот View

<GroupBox Header="Горячие слова">
                <ListBox ItemsSource="{Binding SelectedCategory.CategorySearchWords}" SelectedItem="{Binding SelectedCategorySearchWord}">
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        </Style>
                    </ListBox.ItemContainerStyle>
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <TextBlock Text="{Binding Name}" 
                                           VerticalAlignment="Center" FontSize="16"/>
                            &lt;Button Content=&quot;X&quot; 
                                    Command=&quot;{Binding DataContext.RemoveCategorySearchWordCommand, RelativeSource={RelativeSource AncestorType=ListBox}}&quot;
                                    CommandParameter=&quot;{Binding}&quot;
                                    HorizontalAlignment=&quot;Right&quot; Width=&quot;50&quot;/&gt;
                        &lt;/Grid&gt;

                    &lt;/DataTemplate&gt;
                &lt;/ListBox.ItemTemplate&gt;
            &lt;/ListBox&gt;

        &lt;/GroupBox&gt;

Команда во ViewModel

#region Command RemoveCategorySearchWordCommand - Remove category
    /// &lt;summary&gt;Remove category&lt;/summary&gt;
    private ICommand _RemoveCategorySearchWordCommand;

    /// &lt;summary&gt;Remove category&lt;/summary&gt;
    public ICommand RemoveCategorySearchWordCommand =&gt; _RemoveCategorySearchWordCommand
        ??= new LambdaCommand(OnRemoveCategorySearchWordCommandExequted, CanRemoveCategorySearchWordCommandExequt);

    /// &lt;summary&gt;Checking the possibility of execution - Remove category&lt;/summary&gt;
    public bool CanRemoveCategorySearchWordCommandExequt(object p) =&gt;
        p is CategorySearchWord categorySearchWord
        &amp;&amp; SelectedCategorySearchWord is not null
        &amp;&amp; categorySearchWord is not null;

    /// &lt;summary&gt;Execution logic - Remove category&lt;/summary&gt;
    public void OnRemoveCategorySearchWordCommandExequted(object p)
    {
        var categorySearchWord_to_remove = p ?? SelectedCategorySearchWord;

        if (!(categorySearchWord_to_remove is CategorySearchWord categorySearchWord)) return;

        if (!_UserDialog.ConfirmInformation($&quot;Вы уверены, что хотите удалить категорию {categorySearchWord.Name}?&quot;, &quot;Удаление категории&quot;)) return;

        _CategorySearchWordRepository.Remove(categorySearchWord.Id);

        SelectedCategory.CategorySearchWords.Remove(categorySearchWord);

        if (ReferenceEquals(SelectedCategorySearchWord, categorySearchWord)) SelectedCategorySearchWord = null;

        UpdateCategorySearchWordsView(SelectedCategory); //перевыбирает SelectedCategory для обновления отображения, 

    }

    #endregion

При добавлении/изменении View корректно отображает/обновляет данные. Единственное, что у меня корректно сработало для обновления View после удаления, это полная перевыгрузка ObservalCollection из БД...Но это хрень какая-то. (OnPropertyChanged(...), удаление и добавление обратно в ObservalCollection SelectedCategory целиком тоже результата не дали)

При том при отладке видно что categorySearchWord как из удаляется как из БД так и из коллекции, а в отображении остаётся висеть, при повторном удалении выкидывает ошибку.

#region CategoryListBox SelectedCategory
    private Category _SelectedCategory;
    /// &lt;summary&gt;SelectedCategory in CategoryListBox&lt;/summary&gt;
    public Category SelectedCategory
    {
        get =&gt; _SelectedCategory;
        set =&gt; Set(ref _SelectedCategory, value);
    }

#endregion

public class Category : NamedEntity //сущность из БД { public ICollection<ProductBase> Products { get; set; } public ICollection<CategorySearchWord> CategorySearchWords { get; set; } }

internal abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;



        protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}



        protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string PropertyName = null)
        {
            if (Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(PropertyName);
            return true;
        }
}

Экспериментирую тут, вот такой вариант обновляет интерфейс, но это тоже не айс

var vvv = SelectedCategory.CategorySearchWords.ToArray();
        SelectedCategory.CategorySearchWords.Clear();  //рабочий вариант, тогда не понятно почему не реагирует на Remove...

        foreach (var item in vvv)
        {
            if (item is null) return;
            SelectedCategory.CategorySearchWords.Add(item);
        }

Heksys
  • 35
  • Что такое SelectedCategory.CategorySearchWords? Покажите участок кода по созданию этого объекта (как SelectedCategory так и CategorySearchWords). – EvgeniyZ Sep 30 '21 at 13:30
  • Добавил, Может это как-то связано с ListBox-ом? Может у него есть какие-то нюансы в отображении данных..? – Heksys Sep 30 '21 at 13:50
  • П.С. Выгрузку из БД, если это для вас важно, делаю через репозиторий в ObservalCollection "CategoryObservalCollection = new ObservableCollection(await _CategoryRepository.Items.ToArrayAsync());" – Heksys Sep 30 '21 at 13:53
  • ICollection<CategorySearchWord> CategorySearchWords - а где вы тут увидели ObservalCollection? Для обновления интерфейса в WPF есть как минимум 2 интерфейса INotifyPropertyChanged - если надо оповестить об изменениях свойства. И INotifyCollectionChanged - если надо оповещать об изменениях данных в коллеции. INotifyCollectionChanged реализуется по умолчанию в- ObservableCollection<T> (ну или можно еще BindingList<T>). Собственно, у вас я каких либо механизмов не вижу. То есть вам достаточно ICollection<CategorySearchWord> заменить на ObservableCollection<CategorySearchWord>. – EvgeniyZ Sep 30 '21 at 13:57
  • Не кажется ли странным вкидывать ObservableCollection в сущность базы данных???? – Heksys Sep 30 '21 at 14:04
  • А это уже косяк с вашей стороны, вашего проектирования. Ведь база это что? Правильно Model, так почему идет к ней привязка напрямую из View слоя, который вообще не должен знать что-либо про Model? В MVVM должно быть Model <-> ViewModel < View, то есть мы по клику отправляем команду в VM, та запрашивает у M слоя нужные данные, сам M слой уже идет в базу и формирует ответ, отдавая обратно в VM, а VM подготавливает свойства для общения с V слоем. Ну а у вас просто идет Model и View, без связующего ViewModel) – EvgeniyZ Sep 30 '21 at 14:10
  • Я использую в проекте загрузку данных через репозиторий, вынесенный в отдельную dll, смысл мне создавать дополнительно модели. А даже если делать полноценный слой моделей, то как реализовывать тогда работу репозиториев, учитывая, что у меня так же используется DI? Как на мой взгляд, получится крайне перегруженный код. – Heksys Sep 30 '21 at 14:31
  • Вы спросили - почему не обновляется? Я вам сказал почему - за обновление UI отвечает INotify**, которого у вас нет. Не хотите его использовать - пересоздавайте по новой коллекцию, ваше право. Перегруженность - ну, эта перегруженность дает вам преимущества и строгий контроль всего и вся. dll и DI - а это тут при чем? У вас нарушение MVVM подхода, проектирования. Вон к примеру я давал ответ, все в своих dll, по своим проектам, связи между друг другом нету, но это же не повод нарушать MVVM... – EvgeniyZ Sep 30 '21 at 14:39
  • Переписал, заработало. Единственный момент для уточнения. В паттерне MWWM Model по факту только описывает используемые в приложении данные и не является их источником. – Heksys Oct 01 '21 at 13:12
  • Не MWWM, а MVVM (от слова View). Model - это данные, без разницы какие. К примеру - работа с базой данных; или работа с текстовым документом; или общение с сайтом через API или др. механизмы; ну или, к примеру общение с неким устройством (а-ля датчик температуры). То есть в Model делается логика, которая общается с тем или иным источником данных, предоставляя для VM слоя нужное, удобные методы получения необходимого. Вот зачем VM знать, как подключаться к базе, если ей надо только узнать какой баланс у Иванова? Достаточно model.GetBalance("Иванов");. Не ленитесь разбивать все на слои! – EvgeniyZ Oct 01 '21 at 13:55
  • С название паттерна да, перепутал. А вот с тем, что модель модель обязательно является источником данных - не согласен. Смотрите, вместо того, чтобы каждый раз писать CRUD для каждой модели, я реализовал: – Heksys Oct 01 '21 at 14:27
  • public interface IRepository<T> where T : class, IEntity, new(){ IQueryable<T> Items { get; } T Get(int id); Task<T> GetAsync(int id, CancellationToken Cancel = default); T Add(T item); Task<T> AddAsync(T item, CancellationToken Cancel = default); void Update(T item); Task UpdateAsync(T item, CancellationToken Cancel = default); void Remove(int id); Task RemoveAsync(int id, CancellationToken Cancel = default); } – Heksys Oct 01 '21 at 14:27
  • И реализовал использование через контейнер сервисов. На мой взгляд так намного лучше – Heksys Oct 01 '21 at 14:28
  • Я вам сейчас говорю про то, как должно быть на самом деле, как идет по правилам MVVM. M - данные, VM - связующий, V - интерфейс. Не нравятся эти правила - дело ваше, вас не заставляют, авось изобретете свой велосипед. так намного лучше - как лучше, я вам давал ссылку выше. То есть в проекте должна быть минимальная связь, не должно быть вообще new(), не должно быть ссылок на другой компонент, каждый класс и метод должны быть самостоятельными, отвечать за одну конкретную задачу, другие подходы имею в себе уйму косяков в дальнейшем (прим: внедрения нового функционала или тестирование). – EvgeniyZ Oct 01 '21 at 14:37
  • И как вы тогда реализуете связь с БД в моделях? – Heksys Oct 01 '21 at 14:40
  • Я вроде вам уже все сказал, нет? В M вы пишете всю логику работы с базой (подключение и др.), она внутренняя. Наружу выдаете простые методы по типу Add(), Get(), Update(), Delete() (это уже от задачи). Дале делаете VM, в ней инициализируете M базы, делаете свойства к которым привяжется V, делаете команды, например, public ICommand AddUserCommand;, по которой будет вызваться void AddUser(User user) => baseModel.Add(user);. Или вон как в валем случае, создание коллекции по команде или как надо - void GetUsers() => Users = new(baseModel.Get(...));. – EvgeniyZ Oct 01 '21 at 14:57

1 Answers1

0

Дописал вот такую модель данных, всё заработало.

class CategoryModel : Category
    {
        public CategoryModel()
        {
            ProductsObsColl = (Products is null) ? new ObservableCollection<ProductBase>() : new ObservableCollection<ProductBase>(Products);
            CategorySearchWordsObsColl = (CategorySearchWords is null) ? new ObservableCollection<CategorySearchWord>() : new ObservableCollection<CategorySearchWord>(CategorySearchWords);
        }
    public CategoryModel(Category category)
    {
        Id = category.Id;
        Name = category.Name;
        Products = category.Products;
        CategorySearchWords = category.CategorySearchWords;

        ProductsObsColl = (Products is null) ? new ObservableCollection&lt;ProductBase&gt;() : new ObservableCollection&lt;ProductBase&gt;(Products);
        CategorySearchWordsObsColl = (CategorySearchWords is null) ? new ObservableCollection&lt;CategorySearchWord&gt;() : new ObservableCollection&lt;CategorySearchWord&gt;(CategorySearchWords);
    }
    public ObservableCollection&lt;ProductBase&gt; ProductsObsColl { get; set; }

    public ObservableCollection&lt;CategorySearchWord&gt; CategorySearchWordsObsColl { get; set; }

}

Переопределил шаблоны во ViewModel, пара нюансов с конкретизацией сущностей и заработало.

Heksys
  • 35