Вы не должны выполнять вычисления или любые трудоемкие операции в получателе или установщике свойства.Это может резко ухудшить производительность.Если вычисления или операция отнимают много времени, вы должны выполнить их в фоновом потоке и вызвать событие PropertyChanged
после завершения Task
.Таким образом, вызов метода получения или установки свойства не замораживает пользовательский интерфейс.
Объяснение наблюдаемого вами поведения:
Побочный эффект изменения значения свойства в своем собственном getter вместо установщика заключается в том, что новое значение не будет распространяться на цели привязки.Получатель вызывается только привязкой, когда произошло событие PropertyChanged
.Таким образом, выполнение вычислений в геттере не приводит к обновлению привязки.Теперь при перезагрузке страницы все привязки будут инициализировать цель привязки и, следовательно, вызывать метод получения свойства.
Необходимо установить свойство TotalPrice
(а не поле поддержки), чтобы инициировать обновление цели привязки.Но, как вы уже испытали, повышение значения PropertyChanged
свойства в том же геттере приведет к бесконечному циклу и, следовательно, к StackOverflowException
.
. Кроме того, вычисление всегда будет выполняться при каждом обращении к геттеру свойства.- даже если TotalPrice
не изменилось.
Значение TotalPrice
зависит от свойства Products
.Чтобы свести к минимуму вероятность вычисления TotalPrice
, рассчитывайте только при изменении Products
:
OrderViewModel.cs
public class OrderViewModel : ViewModelBase
{
private decimal _totalPrice;
public decimal TotalPrice
{
get => this._totalPrice;
set
{
if (this._totalPrice == value)
return;
this._totalPrice = value;
RaisePropertyChanged();
}
}
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get => this._products;
set
{
if (this.Products == value)
return;
if (this.Products != null)
{
this.Products.CollectionChanged -= OnCollectionChanged;
UnsubscribeFromItemsPropertyChanged(this.Products);
}
this._products = value;
this.Products.CollectionChanged += OnCollectionChanged;
if (this.Products.Any())
{
SubscribeToItemsPropertyChanged(this.Products);
}
RaisePropertyChanged();
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!e.Action.Equals(NotifyCollectionChangedAction.Move))
{
UnsubscribeFromItemsPropertyChanged(e.OldItems);
SubscribeToItemsPropertyChanged(e.NewItems);
}
CalculateTotalPrice();
}
private void ProductChanged(object sender, PropertyChangedEventArgs e) => CalculateTotalPrice();
private void SubscribeToItemsPropertyChanged(IList newItems) => newItems?.OfType<INotifyPropertyChanged>().ToList().ForEach((item => item.PropertyChanged += ProductChanged));
private void UnsubscribeFromItemsPropertyChanged(IEnumerable oldItems) => oldItems?.OfType<INotifyPropertyChanged>().ToList().ForEach((item => item.PropertyChanged -= ProductChanged));
private void CalculateTotalPrice() => this.TotalPrice = this.Products.Sum(item => item.total_price);
private void GetProducts()
{
using (var context = new mainEntities())
{
var result = context.product.Include(c => c.brand);
this.Products = new ObservableCollection<Product>(
result.Select(item => new Product(item.name, item.mass, item.ean, item.brand.name, item.price)));
}
}
public void ResetOrder()
{
this.Products
.ToList()
.ForEach(product => product.Reset());
this.TotalPrice = 0;
}
public OrderViewModel()
{
SetView("Dodaj zamówienie");
GetProducts();
}
}
Также убедитесь, что Product
(предметы из коллекции Products
) тоже реализуют INotifyPropertyChanged
.Это обеспечит возникновение события Products.CollectionChanged
при изменении свойств Product
.
Чтобы исправить поведение переключения страниц, необходимо изменить класс MainViewModel
:
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
// The page viewmodels
private Dictionary<string, ViewModelBase> PageViewModels { get; set; }
public Stack<string> ViewsQueue;
public MainViewModel()
{
User = new User(1, "login", "name", "surname", 1, 1, 1);
this.PageViewModels = new Dictionary<string, ViewModelBase>()
{
{"login", new LoginViewModel()},
{"menu", new MenuViewModel()},
{"order", new OrderViewModel()},
{"clients", new ClientsViewModel(User)}
};
this.CurrentViewModel = this.PageViewModels["login"];
this.ViewsQueue = new Stack<string>();
this.ViewsQueue.Push("login");
Messenger.Default.Register<NavigateTo>(
this,
(message) =>
{
try
{
ViewsQueue.Push(message.Name);
if (message.user != null) User = message.user;
SwitchView(message.Name);
}
catch (System.InvalidOperationException e)
{
}
});
Messenger.Default.Register<GoBack>(
this,
(message) =>
{
try
{
ViewsQueue.Pop();
SwitchView(ViewsQueue.Peek());
}
catch (System.InvalidOperationException e)
{
}
});
}
public RelayCommand<string> GoTo => new RelayCommand<string>(
viewName =>
{
ViewsQueue.Push(viewName);
SwitchView(viewName);
});
protected void SwitchView(string name)
{
if (this.PageViewModels.TryGetValue(name, out ViewModelBase nextPageViewModel))
{
if (nextPageViewModel is OrderViewModel orderViewModel)
orderViewModel.ResetOrder();
this.CurrentViewModel = nextPageViewModel;
}
}
}
Ваш измененный Product.cs
public class Product : ViewModelBase
{
public long id { get; set; }
public string name { get; set; }
public decimal mass { get; set; }
public long ean { get; set; }
public long brand_id { get; set; }
public string img_source { get; set; }
public string brand_name { get; set; }
private decimal _price;
public decimal price
{
get => this._price;
set
{
if (this._price == value)
return;
this._price = value;
OnPriceChanged();
RaisePropertyChanged();
}
}
private long _amount;
public long amount
{
get => this._amount;
set
{
if (this._amount == value)
return;
this._amount = value;
OnAmountChanged();
RaisePropertyChanged();
}
}
private decimal _total_price;
public decimal total_price
{
get => this._total_price;
set
{
if (this._total_price == value)
return;
this._total_price = value;
RaisePropertyChanged();
}
}
public Product(long id, string name, decimal mass, long ean, long brandId, decimal price, string imgSource)
{
this.id = id;
this.name = name;
this.mass = mass;
this.ean = ean;
this.brand_id = brandId;
this.price = price;
this.img_source = imgSource;
}
public Product(string name, decimal mass, long ean, string brandName, decimal price)
{
this.id = this.id;
this.name = name;
this.mass = mass;
this.ean = ean;
this.brand_name = brandName;
this.price = price;
}
public void Reset()
{
// Resetting the `amount` will trigger recalculation of `total_price`
this.amount = 0;
}
protected virtual void OnAmountChanged()
{
CalculateTotalPrice();
}
private void OnPriceChanged()
{
CalculateTotalPrice();
}
private void CalculateTotalPrice()
{
this.total_price = this.price * this.amount;
}
}
Проблема заключалась в том, что вы всегда создавали новую модель представления при переключении настр.Конечно, вся информация на предыдущей странице теряется.Вы должны повторно использовать тот же экземпляр модели представления.Для этого просто сохраните их в выделенном частном свойстве, которое вы инициализируете один раз в конструкторе.