реализую проект 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"/>
<Button Content="X"
Command="{Binding DataContext.RemoveCategorySearchWordCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
HorizontalAlignment="Right" Width="50"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
Команда во ViewModel
#region Command RemoveCategorySearchWordCommand - Remove category
/// <summary>Remove category</summary>
private ICommand _RemoveCategorySearchWordCommand;
/// <summary>Remove category</summary>
public ICommand RemoveCategorySearchWordCommand => _RemoveCategorySearchWordCommand
??= new LambdaCommand(OnRemoveCategorySearchWordCommandExequted, CanRemoveCategorySearchWordCommandExequt);
/// <summary>Checking the possibility of execution - Remove category</summary>
public bool CanRemoveCategorySearchWordCommandExequt(object p) =>
p is CategorySearchWord categorySearchWord
&& SelectedCategorySearchWord is not null
&& categorySearchWord is not null;
/// <summary>Execution logic - Remove category</summary>
public void OnRemoveCategorySearchWordCommandExequted(object p)
{
var categorySearchWord_to_remove = p ?? SelectedCategorySearchWord;
if (!(categorySearchWord_to_remove is CategorySearchWord categorySearchWord)) return;
if (!_UserDialog.ConfirmInformation($"Вы уверены, что хотите удалить категорию {categorySearchWord.Name}?", "Удаление категории")) 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;
/// <summary>SelectedCategory in CategoryListBox</summary>
public Category SelectedCategory
{
get => _SelectedCategory;
set => 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);
}
SelectedCategory.CategorySearchWords? Покажите участок кода по созданию этого объекта (какSelectedCategoryтак иCategorySearchWords). – EvgeniyZ Sep 30 '21 at 13:30ICollection<CategorySearchWord> CategorySearchWords- а где вы тут увиделиObservalCollection? Для обновления интерфейса в WPF есть как минимум 2 интерфейсаINotifyPropertyChanged- если надо оповестить об изменениях свойства. ИINotifyCollectionChanged- если надо оповещать об изменениях данных в коллеции.INotifyCollectionChangedреализуется по умолчанию в-ObservableCollection<T>(ну или можно ещеBindingList<T>). Собственно, у вас я каких либо механизмов не вижу. То есть вам достаточноICollection<CategorySearchWord>заменить наObservableCollection<CategorySearchWord>. – EvgeniyZ Sep 30 '21 at 13:57Model, так почему идет к ней привязка напрямую изViewслоя, который вообще не должен знать что-либо проModel? В MVVM должно бытьModel <-> ViewModel < View, то есть мы по клику отправляем команду вVM, та запрашивает уMслоя нужные данные, самMслой уже идет в базу и формирует ответ, отдавая обратно вVM, аVMподготавливает свойства для общения сVслоем. Ну а у вас просто идетModelиView, без связующегоViewModel) – EvgeniyZ Sep 30 '21 at 14:10INotify**, которого у вас нет. Не хотите его использовать - пересоздавайте по новой коллекцию, ваше право. Перегруженность - ну, эта перегруженность дает вам преимущества и строгий контроль всего и вся.dllиDI- а это тут при чем? У вас нарушение MVVM подхода, проектирования. Вон к примеру я давал ответ, все в своих dll, по своим проектам, связи между друг другом нету, но это же не повод нарушать MVVM... – EvgeniyZ Sep 30 '21 at 14:39MWWM, аMVVM(от слова View).Model- это данные, без разницы какие. К примеру - работа с базой данных; или работа с текстовым документом; или общение с сайтом через API или др. механизмы; ну или, к примеру общение с неким устройством (а-ля датчик температуры). То есть в Model делается логика, которая общается с тем или иным источником данных, предоставляя для VM слоя нужное, удобные методы получения необходимого. Вот зачем VM знать, как подключаться к базе, если ей надо только узнать какой баланс у Иванова? Достаточноmodel.GetBalance("Иванов");. Не ленитесь разбивать все на слои! – EvgeniyZ Oct 01 '21 at 13:55public 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:27M- данные,VM- связующий,V- интерфейс. Не нравятся эти правила - дело ваше, вас не заставляют, авось изобретете свой велосипед.так намного лучше- как лучше, я вам давал ссылку выше. То есть в проекте должна быть минимальная связь, не должно быть вообщеnew(), не должно быть ссылок на другой компонент, каждый класс и метод должны быть самостоятельными, отвечать за одну конкретную задачу, другие подходы имею в себе уйму косяков в дальнейшем (прим: внедрения нового функционала или тестирование). – EvgeniyZ Oct 01 '21 at 14:37Mвы пишете всю логику работы с базой (подключение и др.), она внутренняя. Наружу выдаете простые методы по типу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