首页 > 解决方案 > 如何更改 ViewModel 属性并将其保存到我的所有页面?

问题描述

我有默认MasterDetailPage项目,其中有一个ItemPage和一个ItemDetailPage(当我单击一个项目时)。在我的ItemDetailPage我将Item.Text属性更改为batata并且我期望ItemPage Item文本会更改,但它没有。如何更改我的属性ItemDetailPage并更改它ItemPage

ItemPage Xaml 代码

<StackLayout>
  <ListView x:Name="ItemsListView"
            ItemsSource="{Binding Items}"
            VerticalOptions="FillAndExpand"
            HasUnevenRows="true"
            RefreshCommand="{Binding LoadItemsCommand}"
            IsPullToRefreshEnabled="true"
            IsRefreshing="{Binding IsBusy, Mode=OneWay}"
            CachingStrategy="RecycleElement"
            ItemSelected="OnItemSelected">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout Padding="10">
            <Label Text="{Binding Text}" 
                   LineBreakMode="NoWrap" 
                   Style="{DynamicResource ListItemTextStyle}" 
                   FontSize="16" />
            <Label Text="{Binding Description}" 
                   LineBreakMode="NoWrap"
                   Style="{DynamicResource ListItemDetailTextStyle}"
                   FontSize="13" />
          </StackLayout>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</StackLayout>
public partial class ItemsPage : ContentPage
{
    ItemsViewModel viewModel;

    ItemDetailViewModel itemViewModel;

    public ItemsPage()
    {
        InitializeComponent();

        BindingContext = viewModel = new ItemsViewModel();
    }

    async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        this.BindingContext = null;

        var item = args.SelectedItem as Item;
        if (item == null)
            return;

        if(itemViewModel == null)
            itemViewModel = new ItemDetailViewModel(item);

        await Navigation.PushAsync(new ItemDetailPage(itemViewModel));

        // Manually deselect item.
        ItemsListView.SelectedItem = null;

        this.BindingContext = viewModel;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        if (viewModel.Items.Count == 0)
            viewModel.LoadItemsCommand.Execute(null);
    }
}
<StackLayout Spacing="20" Padding="15">
  <Label Text="Text:" FontSize="Medium" />
  <Label Text="{Binding Item.Text}" FontSize="Small"/>
  <Label Text="Description:" FontSize="Medium" />
  <Label Text="{Binding Item.Description}" FontSize="Small"/>
</StackLayout>
public partial class ItemDetailPage : ContentPage
{
    ItemDetailViewModel viewModel;

    public ItemDetailPage(ItemDetailViewModel viewModel)
    {
        InitializeComponent();

        viewModel.Item.Text = "batata";

        BindingContext = this.viewModel = viewModel;
    }
}

项目视图模型

 public class ItemsViewModel : BaseViewModel
    {
        public ObservableCollection<Item> Items { get; set; }
        public Command LoadItemsCommand { get; set; }

        public ItemsViewModel()
        {
            Title = "Browse";
            Items = new ObservableCollection<Item>();
            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

            MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item) =>
            {
                var newItem = item as Item;
                Items.Add(newItem);
                await DataStore.AddItemAsync(newItem);
            });
        }

        async Task ExecuteLoadItemsCommand()
        {
            if (IsBusy)
                return;

            IsBusy = true;

            try
            {
                Items.Clear();
                var items = await DataStore.GetItemsAsync(true);
                foreach (var item in items)
                {
                    Items.Add(item);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
        }
    }

项目详细视图模型

public class ItemDetailViewModel : BaseViewModel
    {
        public Item Item { get; set; }
        public ItemDetailViewModel(Item item = null)
        {
            Title = item?.Text;
            Item = item;
        }
    }

产品型号:

public class Item
    {
        public string Id { get; set; }
        public string Text { get; set; }
        public string Description { get; set; }
    }

标签: c#xamarinxamarin.forms

解决方案


问题是您正在绑定到Item不通知其更改的类的属性,例如TextDescription。因此,当属性的值发生更改时,视图中的值不会更新。

您可以通过实施INotifyPropertyChanged解决它Item

public class Item : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Id { get; set; }

    private string _text;
    public string Text
    {
        get => _text;
        set
        {
            if (_text != value)
            {
                _text = value;
                NotifyPropertyChanged();
            }
        }
    }

    private string _description;
    public string Description
    {
        get => _description;
        set
        {
            if (_description != value)
            {
                _description = value;
                NotifyPropertyChanged();
            }
        }
    }

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

但是,我还建议您不要绑定到嵌套属性,因为这可能会出现问题。因此,无需绑定到Item.Textand Item.Description,而是直接绑定到视图模型中的属性。

例如,您可以创建两个名为and的ItemDetailViewModel属性:ItemTextItemDescription

public string ItemText => Item?.Text;
public string ItemDescription => Item?.Description;
<!-- Updated bindings -->
<StackLayout Spacing="20" Padding="15">
  <Label Text="Text:" FontSize="Medium" />
  <Label Text="{Binding ItemText}" FontSize="Small"/>
  <Label Text="Description:" FontSize="Medium" />
  <Label Text="{Binding ItemDescription}" FontSize="Small"/>
</StackLayout>

为了确保这些属性在相应属性发生更改时通知它们的Item更改,您需要订阅该事件Item.PropertyChanged,以便传播更新:

// (Assuming the base viewmodel implements INotifyPropertyChanged the same way than Item)
public class ItemDetailViewModel : BaseViewModel
{
    private Item _item;
    public Item Item
    {
        get => _item;
        set
        {
            if (_item != value)
            {
                if (_item != null)
                {
                    // Unsubscribe from the old item
                    _item.PropertyChanged -= OnItemPropertyChanged;
                }

                _item = value;
                NotifyPropertyChanged();

                if (value != null)
                {
                    // Subscribe to the new item
                    value.PropertyChanged += OnItemPropertyChanged;
                }

                // Since the entire item has changed, we notify 
                // about changes in all the dependant properties
                Title = Item?.Text;
                NotifyPropertyChanged(nameof(ItemText));
                NotifyPropertyChanged(nameof(ItemDescription));
            }
        }
    }

    public string ItemText => Item?.Text;
    public string ItemDescription => Item?.Description;

    public ItemDetailViewModel(Item item = null)
    {
        Item = item;
    }

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // When a property of the item changes, we propagate the changes
        // to the properties of this viewmodel that now depend on it
        if (e.PropertyName == nameof(Item.Text))
        {
            Title = Item?.Text;
            NotifyPropertyChanged(nameof(ItemText));
        }
        else if (e.PropertyName == nameof(Item.Description))
        {
            NotifyPropertyChanged(nameof(ItemDescription));
        }
    }
}

这段代码有点乱,可以改进以使其更优雅,但我希望你能明白。


推荐阅读