如何按照 MVVM 模式在 MAUI 应用程序中保留数据

问题描述 投票:0回答:1

我已经设置了一个新的 MAUI 应用程序,并开始为 MVVM 模式构建应用程序,其中包含一个用于视图、视图模型和模型的文件夹。根文件夹仅包含 App.xaml 和 AppShell.xaml 以及 MauiProgram.cs.

我已经在 shell 中设置了导航,并且能够导航到不同的视图。 我的观点之一基本上是一个列表,右下角有一个悬停按钮。当我单击此按钮时,我会显示一个包含文本输入和按钮的社区工具包弹出窗口。 当我按下这个按钮时,我想将一个项目添加到列表中。

所以我设置了一个视图“DailyPage”和一个视图模型“DailyViewModel”。 Viewmodel 代码如下所示:

public class DailyViewModel : ViewModelBase
    {
        ObservableRangeCollection<ExampleModel> _DailyList { get; set;}
        public ObservableRangeCollection<ExampleModel> DailyList
        {
            get => _DailyList;
            set
            {
                _DailyList = value;
            }
        }
        public AsyncCommand RefreshCommand { get; }

        public DailyViewModel()
        {
            Title = "Daily Page";
            AddObjectCommand= new MvvmHelpers.Commands.Command(AddObject);
            RefreshCommand = new AsyncCommand(Refresh);
            _DailyList = new ObservableRangeCollection<ExampleModel>
            {
                new ExampleModel{ Name = "Test1" },
                new ExampleModel{ Name = "Test2" },
                new ExampleModel{ Name = "Test3" },
                new ExampleModel{ Name = "Test4" }
            };
        }
        string addName = "-";
        public string AddName
        {
            get => addName;
            set => SetProperty(ref addName, value);
        }

        public ICommand AddObjectCommand { get; }

        void AddObject()
        {
            ExampleModel ex = new ExampleModel() { Name = AddName };
            _DailyList.Add(ex);
        }

DailyPage 视图如下所示:

<AbsoluteLayout>
        <ListView BackgroundColor="Transparent"
                  AbsoluteLayout.LayoutBounds="0,0,1,1"
                  AbsoluteLayout.LayoutFlags="PositionProportional,HeightProportional,WidthProportional"
                  Margin="10"
                  ItemsSource="{Binding DailyList,Mode=TwoWay}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="model:ExampleModel">
                    <ViewCell>
                        <Grid Padding="5"
                              HorizontalOptions="Center">
                            <Label Text="{Binding Name}"
                                   FontSize="Large"
                                   VerticalOptions="Center"
                                   HorizontalOptions="Center"
                                   Grid.Column="1"/>
                        </Grid>
                       
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Text="+"
                FontSize="Large"
                FontAttributes="Bold"
                AbsoluteLayout.LayoutBounds="1,1,100,100"
                AbsoluteLayout.LayoutFlags="PositionProportional"
                Margin="10"
                Clicked="onAddEntryClicked"/>
    </AbsoluteLayout>

输入数据的条目弹出窗口如下所示:

<toolkit:Popup.BindingContext>
        <viewmodels:DailyViewModel/>
</toolkit:Popup.BindingContext>
    
    <VerticalStackLayout>
        <Label Text="Name"
               FontSize="Large"/>
        <Entry Text="{Binding AddName}"/>
        <Button Command="{Binding AddObjectCommand}"
                Text="Add"/>
    </VerticalStackLayout>

我还在 DailyPage 和 AddEntryPopup 中引用了 DailyViewModel,如下所示:

x:DataType="viewmodels:DailyViewModel"

因此编译数据绑定和 Intellisense 按预期工作。

直到通过命令添加数据,一切都按预期工作。我导航到视图,我看到一个列表,其中包含我添加到视图模型列表中的示例数据。 当我按下“+”按钮时,弹出窗口显示并让我输入数据。 当按下“添加”按钮时,甚至执行“AddObjectCommand”,当我调试它并查看监视列表时,对象已成功添加。

我期待的是,当我将一个对象添加到 ObservableRangeCollection 时,当我为该 observableRangeCollection 设置数据绑定时,稍后会在我的列表中找到该对象。但是我发现每次执行 AddObjectCommand 时,Collection 只包含我设置为演示数据的项目(Test1、Test2、Test3、Test4),而不是我已经添加的那些。

为什么实际视图没有改变,数据却丢失了? 您将如何存储数据以使其在更改视图时不会丢失?

最初我认为当我开始切换视图时会出现问题,因为数据不是全局存储在 MVVM 模式中,但似乎在使用视图模型时也不会保留数据。 这是我第一次真正尝试使用 MVVM 模式。我已经阅读了很多关于它的内容,我认为我已经了解了结构和目标,但我似乎在执行上失败了。

c# .net mvvm data-binding maui
1个回答
0
投票

即使两个页面(

DailyPage
AddEntryPopup
)共享相同的Viewmodel(
DailyViewModel.cs
),也会创建
DailyViewModel.cs
的两个不同的新实例。所以这两个页面不能为
_DailyList 
和变量
public string AddName
共享相同的值。

对于这个问题,可以对两个页面使用两个不同的VM,在页面之间传递新添加的数据(比如可以使用

MessageCenter
传递数据)。

我已经创建了一个demo并实现了这个功能。 可以参考以下代码:

MainPage.xaml

<ContentPage.BindingContext>
    <viewmodels:DailyViewModel></viewmodels:DailyViewModel>
</ContentPage.BindingContext>

 <VerticalStackLayout
        Spacing="25"
        Padding="30,0"
        VerticalOptions="Start">
     
            <ListView BackgroundColor="Transparent"
              Margin="10"
              ItemsSource="{Binding DailyList,Mode=TwoWay}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="models:ExampleModel">
                        <ViewCell>
                            <Grid Padding="5"
                          HorizontalOptions="Center">
                                <Label Text="{Binding Name}"
                               FontSize="Large"
                               VerticalOptions="Center"
                               HorizontalOptions="Center"
                               Grid.Column="1"/>
                            </Grid>

                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <Button Text="+"
            FontSize="Large"
            FontAttributes="Bold"
            AbsoluteLayout.LayoutBounds="1,1,100,100"
            AbsoluteLayout.LayoutFlags="PositionProportional"
            Margin="10"
            Clicked="onAddEntryClicked"/>
 </VerticalStackLayout>

MainPage.xaml.cs

public partial class MainPage : ContentPage 
{
      public MainPage()
      {
            InitializeComponent();
      }

    private async  void onAddEntryClicked(object sender, EventArgs e)
    {
        var addPage = new AddItemPage();
        await Navigation.PushModalAsync(addPage);
    }
}

DailyViewModel.cs

public class DailyViewModel: INotifyPropertyChanged 
{
    ObservableCollection<ExampleModel> _DailyList { get; set; }
    public ObservableCollection<ExampleModel> DailyList
    {
        get => _DailyList;
        set
        {
            _DailyList = value;
        }
    }
    public ICommand RefreshCommand { get; }

    public DailyViewModel()
    {
        _DailyList = new ObservableCollection<ExampleModel>
        {
            new ExampleModel{ Name = "Test1" },
            new ExampleModel{ Name = "Test2" },
            new ExampleModel{ Name = "Test3" },
            new ExampleModel{ Name = "Test4" }
        };


        MessagingCenter.Subscribe<AddItemViewModel, ExampleModel>(this, "NewItem", (obj, item) =>
        {
            var newItem = item as ExampleModel;

            _DailyList.Add(newItem);
        });


    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;

}

AddItemPage.xaml

<?xml version="1.0" encoding="utf-8" ?> 
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAddItemToListApp.Views.AddItemPage"
             xmlns:viewmodels="clr-namespace:MauiAddItemToListApp.ViewModels"
             Title="AddItemPage">

    <!--<ContentPage.BindingContext>
        <viewmodels:AddItemViewModel></viewmodels:AddItemViewModel>
    </ContentPage.BindingContext>-->
    <VerticalStackLayout>
        <Label Text="Name"
               FontSize="Large"/>
        <Entry Text="{Binding AddName}"/>
        <Button Command="{Binding AddObjectCommand}"
                Text="Add"/>
    </VerticalStackLayout>
</ContentPage>

AddItemPage.xaml.cs

public partial class AddItemPage : ContentPage 
{
      public AddItemPage()
      {
            InitializeComponent();

            this.BindingContext =  new AddItemViewModel(Navigation);
      }
}

AddItemViewModel.cs

public class AddItemViewModel: INotifyPropertyChanged 
{
    public INavigation Navigation { get; set; }

    public AddItemViewModel(INavigation navigation)
    {
        AddObjectCommand = new Command(addItem);
        this.Navigation = navigation;
    }

    string addName = "";
    public string AddName
    {
        get => addName;
        set => SetProperty(ref addName, value);
    }

    public ICommand AddObjectCommand { get; }

    private async void addItem()
    {
        ExampleModel newItem = new ExampleModel();
        newItem.Name = AddName;
        //send message
        MessagingCenter.Send(this, "NewItem", newItem);

        await Navigation.PopModalAsync();
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
© www.soinside.com 2019 - 2024. All rights reserved.