如何更改ViewModel属性并将其保存到我的所有页面?

时间:2019-05-08 14:08:26

标签: c# xamarin xamarin.forms

我有一个默认的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;
    }
}

ItemsViewModel

 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;
            }
        }
    }

ItemsDetailViewModel

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

ItemModel:

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

2 个答案:

答案 0 :(得分:2)

问题是您绑定到类Item的属性,这些属性不会通知其更改,例如TextDescription。因此,更改属性值后,视图中的值不会更新。

您可以通过Item中的implementing INotifyPropertyChanged解决此问题:

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.TextItem.Description,只需直接绑定到视图模型中的属性即可。

例如,在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));
        }
    }
}

此代码有点混乱,可以对其进行改进以使其更优雅,但希望您能理解。

答案 1 :(得分:0)

一种解决方案是将其绑定到静态属性

public static class MyClass
{
    public static string MyProperty { get; set; }
}

在xaml中添加对MyClass文件位置的引用,并绑定到静态属性

{x:Static local:MyClass.MyProperty}">