簡體   English   中英

如何實時更新文本塊?

[英]How to update textblock in real-time?

我正在使用MVVM Light框架進行項目。
我有MainViewModel,它可以幫助我在視圖模型之間導航。 我有GoBack和GoTo方法。 他們正在改變CurrentViewModel。

private RelayCommand<string> _goTo;
public RelayCommand<string> GoTo
        {
            get
            {
                return _goTo
                    ?? (_goTo = new RelayCommand<string>(view
                     =>
                    {
                        SwitchView(view);
                    }));
            }
        }
 private void SwitchView(string name)
        {
            switch (name)
            {
                case "login":
                    User = null;
                    CurrentViewModel = new LoginViewModel();
                    break;
                case "menu":
                    CurrentViewModel = new MenuViewModel();
                    break;
                case "order":
                    CurrentViewModel = new OrderViewModel();
                    break;
            }

在MainWindow中,有內容控件和數據模板。

[...]
 <DataTemplate DataType="{x:Type vm:LoginViewModel}">
            <view:Login/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:MenuViewModel}">
            <view:Menu/>
        </DataTemplate>
[...]

<ContentControl VerticalAlignment="Top" HorizontalAlignment="Stretch" 
                            Content="{Binding CurrentViewModel}" IsTabStop="false"/>

在我的OrderView中(它是UserControl),我有一個應該顯示訂單的TotalPrice的文本塊。

<TextBlock Text="{Binding AddOrderView.TotalPrice}"  Padding="0 2 0 0" FontSize="20" FontWeight="Bold" HorizontalAlignment="Right"/>

OrderViewModel具有屬性TotalPrice,並且效果很好。 當我調試時,我看到它已經更改,但是在我的視圖中什么也沒發生。

        private decimal _totalPrice;
        public decimal TotalPrice
        {
            get
            {
                _totalPrice = 0;
                foreach (var item in Products)
                {
                    item.total_price = item.amount * item.price;
                    _totalPrice += item.price * item.amount;
                }
                return _totalPrice;
            }
            set
            {
                if (_totalPrice == value)
                    return;
                _totalPrice = value;
                RaisePropertyChanged("TotalPrice");
            }
        }

OrderViewModel從BaseViewModel繼承而來,並實現了INotifyPropertyChanged。

為什么我的文本塊無法更新/刷新? 這個怎么做?

當我使用后退按鈕更改視圖並再次轉到OrderView時,我看到了更改!

我花了幾天時間尋找解決方案,但沒有任何幫助。

https://i.stack.imgur.com/K8lip.gif

因此,看起來好像在設置View時,沒有重新加載就無法更改它。 我不知道它是如何工作的。

您不應該在屬性的getter或setter中進行計算或進行任何耗時的操作。 這會大大降低性能。 如果計算或操作很耗時,則應在后台線程中執行它,並在Task完成后引發PropertyChanged事件。 這樣,對屬性的getter或setter的調用不會凍結UI。

您觀察到的行為的解釋:
用自己的getter而不是setter更改屬性值的副作用是新值不會傳播到綁定目​​標。 僅當發生PropertyChanged事件時,綁定才調用getter。 因此,在getter中進行計算不會觸發綁定刷新。 現在,當重新加載頁面時,所有綁定都將初始化綁定目標,並因此調用屬性getter。

您必須設置TotalPrice屬性(而不是后備字段)才能觸發綁定目標的刷新。 但是,正如您已經體驗到的那樣,在同一getter中引發屬性的PropertyChanged事件將導致無限循環,並因此導致StackOverflowException
此外,只要訪問屬性的getter,即使總TotalPrice未更改,也將始終執行計算。

TotalPrice的值取決於Products屬性。 為了最大程度地減少TotalPrice計算的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();
  }
}

還要確保ProductProducts集合中的項目)也實現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;
  }
}

問題是,切換到頁面時,您始終創建了新的視圖模型。 當然,所有先前的頁面信息都會丟失。 您必須重用相同的視圖模型實例。 為此,只需將它們存儲在專用的私有屬性中,即可在構造函數中對其進行一次初始化。

它沒有更新,因為您只調用RaisePropertyChanged("TotalPrice"); 在二傳手。 而在您的getter中是計算。 因此,無論何時更改Products屬性或Products集合的內容,都還需要調用RaisePropertyChanged("TotalPrice"); 通知View TotalPrice已更新。

因此,如果您更改了item.amount或item.price中的任何一個,或者從Products列表中添加或刪除了項目,則也需要致電。 RaisePropertyChanged("TotalPrice");

例如:

Products.Add(item);
RaisePropertyChanged("TotalPrice"); //This will tell you're View to check for the new value from TotalPrice

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM