问题
So I have a problem where I want to pass data around my application in what I thought would have been a very standard way, but I'm unable to find any samples or examples that are similar to what I want to do.
For example, I have an application with the type person.
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
I want to create a list of person cards, each displaying the person's information. Then, when the card is tapped, it will take you to a new page containing that person's information. I have achieved this functionality in the following code, however the xamarin principles I tried didn't work, so it relies more on my c# skills, and I feel like this is not the standard way to write xamarin code.
I have uploaded the working project to a GitHub repo.
So my questions are, what are the disadvantages of my approach, and how would I implement the same functionality using more standard tools like bindings.
PersonCard.xaml:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomViewListTest.Views.PersonCard"
xmlns:System="clr-namespace:System;assembly=mscorlib">
<ContentView.ControlTemplate>
<ControlTemplate>
<Frame BorderColor="#3F3F3F" Margin="2">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped"/>
</Frame.GestureRecognizers>
<StackLayout>
<Label Text="{TemplateBinding Name}"></Label>
<Label Text="{TemplateBinding Age}"></Label>
</StackLayout>
</Frame>
</ControlTemplate>
</ContentView.ControlTemplate>
</ContentView>
PersonCard.cs
[XamlCompilation(XamlCompilationOptions.Compile)] public partial class PersonCard : ContentView {
public PersonCard(Person person)
{
Person = person;
InitializeComponent();
}
public PersonCard()
{
InitializeComponent();
}
public static readonly BindableProperty PersonProperty =
BindableProperty.Create(nameof(Person), typeof(Person), typeof(PersonCard), null);
public static readonly BindableProperty NameProperty =
BindableProperty.Create(nameof(Name), typeof(string), typeof(PersonCard), string.Empty);
public static readonly BindableProperty AgeProperty =
BindableProperty.Create(nameof(Age), typeof(string), typeof(PersonCard), string.Empty);
public Person Person
{
get => (Person)GetValue(PersonProperty);
set => SetValue(PersonProperty, value);
}
public string Name
{
get { return Person.Name; }
}
public string Age
{
get { return Person.Age.ToString(); }
}
async void OnTapped(object sender, EventArgs args)
{
Page page = new NavigationPage(new PersonFullView(Person));
await Navigation.PushAsync(new NavigationPage(page));
}
}
PersonFullView.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomViewListTest.Views.PersonFullView"
xmlns:views ="clr-namespace:CustomViewListTest.Views">
<StackLayout>
<Label Text="This page shows a person's details"></Label>
<Label x:Name="PersonName"></Label>
<Label x:Name="PersonAge"></Label>
<Label Text="End"></Label>
</StackLayout>
</ContentPage>
PersonFullView.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PersonFullView : ContentPage
{
public PersonFullView()
{
InitializeComponent();
}
public Person Person;
public PersonFullView(Person person)
{
Person = person;
InitializeComponent();
PersonName.Text = person.Name;
PersonAge.Text = person.Age.ToString();
}
public static readonly BindableProperty NameProperty =
BindableProperty.Create(nameof(Name), typeof(string), typeof(PersonFullView), "No name");
public static readonly BindableProperty AgeProperty =
BindableProperty.Create(nameof(Age), typeof(string), typeof(PersonFullView), "No age");
public string Name
{
get { return Person.Name; }
}
public string Age
{
get { return Person.Age.ToString(); }
}
}
PersonList.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomViewListTest.Views.PersonList"
xmlns:views ="clr-namespace:CustomViewListTest.Views">
<StackLayout x:Name="personStack">
<Label Text="List of People"></Label>
</StackLayout>
</ContentPage>
PersonList.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PersonList : ContentPage
{
public PersonList()
{
InitializeComponent();
}
public List<Person> People;
public List<PersonCard> cards { get; set; }
public PersonList(List<Person> people)
{
InitializeComponent();
People = people;
cards = new List<PersonCard>();
foreach (var p in people)
{
personStack.Children.Add(new PersonCard(p));
}
BindingContext = this;
}
}
The way it works is that in personList, it defines 2 Person objects, then uses the PersonCard constructor which takes a person object, to create the cards. I've used a stackLayout because I couldn't get the same functionality to work with a listview, which I would prefer. I think the problem stems from a list view not being able to hold contentviews, instead it holds lists of objects and you give it a bindable template. I couldn't get that working.
You may also notice I got the bindable properties working for PersonCard, but not for PersonFullView. I tried using basically identical code for the latter, but to no avail. I think something about the bindable properties on a page that doesn't exist at startup is fishy.
So, I'm curious how I would achieve this using Bindings, instead of create non-parameterless contructors for my views and pages, which I've not really seen in any examples. Also, I'm interested in moving as much logic to the xaml files. Obviously you can do it all from the cs file behind, but I find that very hard to get the layout right.
回答1:
Since you had used Data-Binding , it would be better to handle logic (fill data and navigation) in ViewModel and use Command instead of Click Event .
Also, I'm interested in moving as much logic to the xaml files
I couldn't get the same functionality to work with a listview, which I would prefer.
So you could modify and improve the code like following . I used ListView to display the data .
in PersonCard
using CustomViewListTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomViewListTest.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PersonCard : ContentView
{
public PersonCard()
{
InitializeComponent();
}
public static readonly BindableProperty NameProperty =
BindableProperty.Create(nameof(Name), typeof(string), typeof(PersonCard), string.Empty);
public static readonly BindableProperty AgeProperty =
BindableProperty.Create(nameof(Age), typeof(string), typeof(PersonCard), string.Empty);
public string Name
{
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public string Age
{
get => (string)GetValue(AgeProperty);
set => SetValue(AgeProperty, value);
}
public static readonly BindableProperty ClickCommandProperty =
BindableProperty.Create(nameof(ClickCommand), typeof(ICommand), typeof(PersonCard));
public ICommand ClickCommand
{
get => (ICommand)GetValue(ClickCommandProperty);
set => SetValue(ClickCommandProperty, value);
}
public static BindableProperty ClickParameterProperty =
BindableProperty.Create(nameof(ClickParameter), typeof(object), typeof(PersonCard));
public object ClickParameter
{
get => (object)GetValue(ClickParameterProperty);
set => SetValue(ClickParameterProperty, value);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomViewListTest.Views.PersonCard"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Name="card"
>
<ContentView.Content>
<Frame BorderColor="#3F3F3F" Padding="0" Margin="2">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ClickCommand,Source={x:Reference card}}" CommandParameter="{Binding ClickParameter,Source={x:Reference card}}"/>
</Frame.GestureRecognizers>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name,Source={x:Reference card}}" TextColor="Red"></Label>
<Label Text="{Binding Age,Source={x:Reference card}}" TextColor="Green"></Label>
</StackLayout>
</Frame>
</ContentView.Content>
</ContentView>
in PersonList
using CustomViewListTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Windows.Input;
namespace CustomViewListTest.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PersonList : ContentPage
{
public PersonList()
{
InitializeComponent();
BindingContext = new MyViewModel(this.Navigation);
}
}
public class MyViewModel
{
public ICommand ClickCommand { get; private set; }
INavigation CurrentNavigation;
public ObservableCollection<Person> MyItems {get;set;}
public MyViewModel( INavigation navigation)
{
CurrentNavigation = navigation;
MyItems = new ObservableCollection<Person>() { new Person("Jack",23),new Person("Lucas",25) };
ClickCommand = new Command(async (org) =>
{
var item = org as Person;
PersonFullView page = new PersonFullView(item);
await CurrentNavigation.PushAsync(page);
});
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomViewListTest.Views.PersonList"
xmlns:views ="clr-namespace:CustomViewListTest.Views"
x:Name="page"
>
<StackLayout>
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<views:PersonCard Name="{Binding Name}" Age="{Binding Age}" ClickCommand="{Binding Path=BindingContext.ClickCommand,Source={x:Reference page}}" ClickParameter="{Binding .}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
in App
MainPage = new NavigationPage(new PersonList());
来源:https://stackoverflow.com/questions/63990624/when-to-use-xamarin-bindings-as-opposed-to-passing-objects-to-page-constructors