Такое ощущение, что для этого должно быть какое-то полупростое решение, но я просто не могу его понять.
Редактировать: предыдущий пример показал бесконечный цикл более четко, но это дает немного больше контекста. Ознакомьтесь с предварительным редактированием для быстрого обзора проблемы.
Следующие 2 класса представляют модели представления модели представления модели (MVVM) узор.
/// <summary>
/// A UI-friendly wrapper for a Recipe
/// </summary>
public class RecipeViewModel : ViewModelBase
{
/// <summary>
/// Gets the wrapped Recipe
/// </summary>
public Recipe RecipeModel { get; private set; }
private ObservableCollection<CategoryViewModel> categories = new ObservableCollection<CategoryViewModel>();
/// <summary>
/// Creates a new UI-friendly wrapper for a Recipe
/// </summary>
/// <param name="recipe">The Recipe to be wrapped</param>
public RecipeViewModel(Recipe recipe)
{
this.RecipeModel = recipe;
((INotifyCollectionChanged)RecipeModel.Categories).CollectionChanged += BaseRecipeCategoriesCollectionChanged;
foreach (var cat in RecipeModel.Categories)
{
var catVM = new CategoryViewModel(cat); //Causes infinite loop
categories.AddIfNewAndNotNull(catVM);
}
}
void BaseRecipeCategoriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
categories.Add(new CategoryViewModel(e.NewItems[0] as Category));
break;
case NotifyCollectionChangedAction.Remove:
categories.Remove(new CategoryViewModel(e.OldItems[0] as Category));
break;
default:
throw new NotImplementedException();
}
}
//Some Properties and other non-related things
public ReadOnlyObservableCollection<CategoryViewModel> Categories
{
get { return new ReadOnlyObservableCollection<CategoryViewModel>(categories); }
}
public void AddCategory(CategoryViewModel category)
{
RecipeModel.AddCategory(category.CategoryModel);
}
public void RemoveCategory(CategoryViewModel category)
{
RecipeModel.RemoveCategory(category.CategoryModel);
}
public override bool Equals(object obj)
{
var comparedRecipe = obj as RecipeViewModel;
if (comparedRecipe == null)
{ return false; }
return RecipeModel == comparedRecipe.RecipeModel;
}
public override int GetHashCode()
{
return RecipeModel.GetHashCode();
}
}
.
/// <summary>
/// A UI-friendly wrapper for a Category
/// </summary>
public class CategoryViewModel : ViewModelBase
{
/// <summary>
/// Gets the wrapped Category
/// </summary>
public Category CategoryModel { get; private set; }
private CategoryViewModel parent;
private ObservableCollection<RecipeViewModel> recipes = new ObservableCollection<RecipeViewModel>();
/// <summary>
/// Creates a new UI-friendly wrapper for a Category
/// </summary>
/// <param name="category"></param>
public CategoryViewModel(Category category)
{
this.CategoryModel = category;
(category.DirectRecipes as INotifyCollectionChanged).CollectionChanged += baseCategoryDirectRecipesCollectionChanged;
foreach (var item in category.DirectRecipes)
{
var recipeVM = new RecipeViewModel(item); //Causes infinite loop
recipes.AddIfNewAndNotNull(recipeVM);
}
}
/// <summary>
/// Adds a recipe to this category
/// </summary>
/// <param name="recipe"></param>
public void AddRecipe(RecipeViewModel recipe)
{
CategoryModel.AddRecipe(recipe.RecipeModel);
}
/// <summary>
/// Removes a recipe from this category
/// </summary>
/// <param name="recipe"></param>
public void RemoveRecipe(RecipeViewModel recipe)
{
CategoryModel.RemoveRecipe(recipe.RecipeModel);
}
/// <summary>
/// A read-only collection of this category's recipes
/// </summary>
public ReadOnlyObservableCollection<RecipeViewModel> Recipes
{
get { return new ReadOnlyObservableCollection<RecipeViewModel>(recipes); }
}
private void baseCategoryDirectRecipesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var recipeVM = new RecipeViewModel((Recipe)e.NewItems[0], this);
recipes.AddIfNewAndNotNull(recipeVM);
break;
case NotifyCollectionChangedAction.Remove:
recipes.Remove(new RecipeViewModel((Recipe)e.OldItems[0]));
break;
default:
throw new NotImplementedException();
}
}
/// <summary>
/// Compares whether this object wraps the same Category as the parameter
/// </summary>
/// <param name="obj">The object to compare equality with</param>
/// <returns>True if they wrap the same Category</returns>
public override bool Equals(object obj)
{
var comparedCat = obj as CategoryViewModel;
if(comparedCat == null)
{return false;}
return CategoryModel == comparedCat.CategoryModel;
}
/// <summary>
/// Gets the hashcode of the wrapped Categry
/// </summary>
/// <returns>The hashcode</returns>
public override int GetHashCode()
{
return CategoryModel.GetHashCode();
}
}
Я не буду показывать модели (рецепт и категорию), если это не требуется, но они в основном заботятся о бизнес-логике (например, добавление рецепта в категорию также добавит другой конец ссылки, т. е. если категория содержит рецепт, то рецепт также содержится в этой категории) и в основном определяют, как все пойдет. ViewModels предоставляют удобный интерфейс для привязки данных WPF. Это причина для классов-оболочек
Поскольку бесконечный цикл находится в конструкторе, и он пытается создать новые объекты, я не могу просто установить логический флаг, чтобы предотвратить это, потому что ни один объект никогда не завершается созданием.
Я имею в виду наличие (либо в виде синглтона, либо в конструкторе, либо в обоих случаях) Dictionary<Recipe, RecipeViewModel>
и Dictionary<Category, CategoryViewModel>
, которые будут лениво загружать модели представления, но не создавать новую, если она уже существует, но я этого не сделал. удосужился попытаться посмотреть, сработает ли это, так как уже поздно, и я немного устал иметь дело с этим в течение последних 6 часов или около того.
Нет гарантии, что код здесь скомпилируется, так как я вытащил кучу вещей, которые не имели отношения к рассматриваемой проблеме.