我已经设置了一个新的 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 模式。我已经阅读了很多关于它的内容,我认为我已经了解了结构和目标,但我似乎在执行上失败了。
即使两个页面(
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;
}