问题
I am building social post sharing application using Xamarin.Forms 2.4 and it talks to my API.
I have PostsPage which uses PostDataViewModel to load ObservableCollection to ListView defined in PostsPage. Data template of list views points to view file. However, in Post View Template I can bind to individual properties of My Post Model, but binding to command, which exists on ViewModel doesn't work. I tried x:Reference with no luck.
My Model:
using System;
using System.Collections.Generic;
namespace SOD_APP_V2.Model
{
public class PostDataModel
{
public string ID { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string Image { get; set; }
public string SocialMedia { get; set; }
public string AvailableTime { get; set; }
public string Audiences { get; set; }
public string Topics { get; set; }
public List<PostVersion> Versions { get; set; }
public PostDataModel PostDetails
{
get
{
return this;
}
}
}
public class PostVersion
{
public string SocialMediaID { get; set; }
public string IconPath { get; set; }
public int CharacterCount { get; set; }
public string Message { get; set; }
}
}
My View Model:
namespace SOD_APP_V2.ViewModel
{
public class PostDataViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//Bindable properties
public ObservableCollection<PostDataModel> PostDataCollection { get; set; }
public ICommand LoadMorePostsCommand { get; private set; }
public ICommand RejectPostCommand { get; private set; }
public static JObject SocialmediaSites { get; set; }
public object SelectedItem { get; set; }
bool isLoadMoreEnabled;
public bool IsLoadMoreEnabled
{
get
{
return isLoadMoreEnabled;
}
set
{
if (isLoadMoreEnabled != value)
{
isLoadMoreEnabled = value;
OnPropertyChanged((nameof(IsLoadMoreEnabled)));
}
}
}
string pageTitle;
public string PageTitle
{
get
{
return pageTitle;
}
set
{
if (pageTitle != value)
{
pageTitle = value;
OnPropertyChanged(nameof(PageTitle));
}
}
}
int currentPage = 1;
public int CurrentPage
{
get
{
return currentPage;
}
set
{
if(currentPage != value)
{
currentPage = value;
OnPropertyChanged(nameof(CurrentPage));
}
}
}
public PostDataViewModel()
{
PostDataCollection = new ObservableCollection<PostDataModel>();
SocialmediaSites = default(JObject);
IsLoadMoreEnabled = true;
LoadMorePostsCommand =
new Command(async () => await GetPosts(), () => IsLoadMoreEnabled);
RejectPostCommand = new Command<PostDataModel>((post) =>
{
System.Diagnostics.Debug.WriteLine("Reject command executed");
System.Diagnostics.Debug.WriteLine("Post ID: " + post.ID);
});
string deployment = ConfigController.GetDeploymentName(ApiController.DeploymentDomain);
MessagingCenter.Subscribe<PostsPage, JArray>(this, "translations", (sender, arg) => {
PageTitle = (arg[0].ToString() != "") ? arg[0].ToString() : "Posts from " + deployment;
});
if (deployment != null)
{
//TODO: lang packs
PageTitle = "Posts from " + deployment;
}
}
public async Task<bool> GetPosts()
{
...
}
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
}
}
}
}
My Post Page XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SOD_APP_V2"
xmlns:view="clr-namespace:SOD_APP_V2.View;assembly=SOD_APP_V2"
xmlns:viewModel="clr-namespace:SOD_APP_V2.ViewModel;assembly=SOD_APP_V2"
xmlns:controls="clr-namespace:SOD_APP_V2.Controls;assembly=SOD_APP_V2"
x:Class="SOD_APP_V2.PostsPage" x:Name="PostsPage"
>
<ContentPage.BindingContext>
<viewModel:PostDataViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Vertical" Padding="10, 5, 10, 5">
<Label x:Name="titleLabel" Text="{Binding PageTitle}"
VerticalOptions="Start"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
BackgroundColor="Transparent"
HorizontalOptions="CenterAndExpand" />
<controls:InfiniteListView x:Name="listView"
SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
IsLoadMoreItemsPossible="{Binding IsLoadMoreEnabled}"
LoadMoreInfiniteScrollCommand="{Binding LoadMorePostsCommand}"
IsEnabled="true"
IsBusy="{Binding IsBusy}"
HasUnevenRows="true"
ItemsSource="{Binding PostDataCollection}"
SeparatorVisibility="None">
<controls:InfiniteListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<view:PostViewTemplate/>
</ViewCell>
</DataTemplate>
</controls:InfiniteListView.ItemTemplate>
</controls:InfiniteListView>
</StackLayout>
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="End">
<Label x:Name="infoLabel" Text="test"
Opacity="0"
TextColor="White"
BackgroundColor="#337ab7"
HorizontalTextAlignment="Center">
</Label>
</StackLayout>
</StackLayout>
</ContentPage.Content>
And Finally my Post View template describing each post:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:SOD_APP_V2.Model;assembly=SOD_APP_V2"
xmlns:controls="clr-namespace:SOD_APP_V2.Controls;assembly=SOD_APP_V2"
x:Class="SOD_APP_V2.View.PostViewTemplate">
<ContentView.Resources>
<ResourceDictionary>
<Style TargetType="controls:AwesomeButton">
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="TextColor" Value="White"/>
<Setter Property="BorderRadius" Value="7"/>
<Setter Property="FontFamily" Value="FontAwesome"/>
</Style>
</ResourceDictionary>
</ContentView.Resources>
<ContentView.Content>
<Frame HasShadow="false" CornerRadius="5" IsClippedToBounds="true" OutlineColor="#09478e" Padding="0" Margin="10">
<Grid
Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1" />
<ColumnDefinition Width="1" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="1" />
<ColumnDefinition Width="1" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
</Grid.RowDefinitions>
<StackLayout Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="6" BackgroundColor="#B0C4DB" Padding="5">
<Label x:Name="postTitle" Text="{Binding Title}" TextColor="#09478e" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
<StackLayout Orientation="Horizontal" VerticalOptions="CenterAndExpand" HorizontalOptions="End">
<controls:AwesomeButton Clicked="OnAvailableTimePopupClicked" CommandParameter="{Binding AvailableTime}" Text="" TextColor="#09478e" BorderWidth="0" Margin="-5" BackgroundColor="Transparent" WidthRequest="36" FontSize="24"></controls:AwesomeButton>
<controls:AwesomeButton Clicked="OnAudiencesPopupClicked" CommandParameter="{Binding Audiences}" Text="" TextColor="#09478e" BorderWidth="0" Margin="-5" BackgroundColor="Transparent" WidthRequest="36" FontSize="24"></controls:AwesomeButton>
<controls:AwesomeButton Clicked="OnTopicsPopupClicked" CommandParameter="{Binding Topics}" Text="" TextColor="#09478e" BorderWidth="0" Margin="-5" BackgroundColor="Transparent" WidthRequest="36" FontSize="24"></controls:AwesomeButton>
</StackLayout>
</StackLayout>
<controls:RepeaterView Grid.Row="1" Grid.Column="2"
ItemsSource="{Binding Versions}"
NoOfColumns="3"
>
<controls:RepeaterView.ItemTemplate>
<DataTemplate>
<StackLayout Spacing="0" >
<Label Text="{Binding Message}" IsVisible="false"/>
<Image Source="{Binding IconPath}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnMessageVersionClicked"
NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</DataTemplate>
</controls:RepeaterView.ItemTemplate>
</controls:RepeaterView>
<Image BackgroundColor="White" Grid.Row="3" Grid.Column="2" Source="{Binding Image}" VerticalOptions="FillAndExpand"/>
<Label x:Name="postMessage" BackgroundColor="White" Grid.Row="3" Grid.Column="3" Text="{Binding Message}" TextColor="#09478e" VerticalOptions="FillAndExpand" />
<controls:AwesomeButton Clicked="OnSharePostClicked" CommandParameter="{Binding ID}" BackgroundColor="#5cb85c" Grid.Row="5" Grid.Column="2" Text=" Share" BorderColor="#5cb85c"></controls:AwesomeButton>
<controls:AwesomeButton Clicked="OnSchedulePostClicked" CommandParameter="{Binding ID}" BackgroundColor="#5bc0de" Grid.Row="5" Grid.Column="3" Text=" Schedule" BorderColor="#5bc0de"></controls:AwesomeButton>
<controls:AwesomeButton Clicked="OnEditPostClicked" CommandParameter="{Binding PostDetails}" TextColor="#09478e" BackgroundColor="White" Grid.Row="6" Grid.Column="2" Text=" Edit" BorderColor="#09478e"></controls:AwesomeButton>
<controls:AwesomeButton Command="{Binding Path=DataContext.RejectPostCommand}" CommandParameter="{Binding}" BackgroundColor="#d9534f" Grid.Row="6" Grid.Column="3" Text=" Reject" BorderColor="#d9534f"></controls:AwesomeButton>
<!-- Inner Border -->
<BoxView Grid.Row="2" Grid.RowSpan="3" Grid.Column="1" BackgroundColor="#09478e"></BoxView>
<BoxView Grid.Row="2" Grid.RowSpan="3" Grid.Column="4" BackgroundColor="#09478e"></BoxView>
<BoxView Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="2" BackgroundColor="#09478e"></BoxView>
<BoxView Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="4" BackgroundColor="#09478e"></BoxView>
</Grid>
</Frame>
</ContentView.Content>
As you see in Post View Template, I am trying to bind RejectPostCommand in ViewModel, but it doesn't bind. I tried x:Reference to PostsPage, but it threw me exception, as it couldn't find that Page from my View Template. I need to able to access command somehow. Would anybody have any ideas?
回答1:
Moving PostViewTemplate XAML into PostsPage may have worked, but now you do not have a re-usable template.
You can create a re-usable solution with the following 3 minor changes to your original code.
Add a bindable property to your PostViewTemplate code-behind like so:
public static BindableProperty ParentBindingContextProperty =
BindableProperty.Create(nameof(ParentBindingContext), typeof(object),
typeof(PostViewTemplate), null);
public object ParentBindingContext
{
get { return GetValue(ParentBindingContextProperty); }
set { SetValue(ParentBindingContextProperty, value); }
}
Bind that property to the ViewModel in your PostsPage XAML like so:
<view:PostViewTemplate ParentBindingContext="{Binding Source={x:Reference Home}, Path=BindingContext}"/>
Now you can access your "parent" viewmodel directly from the bindings in your PostViewTemplate, like this (Note that you need to add an x:Name to your ContentView to use as the source of your binding):
<ContentView ... x:Name="PostView" ...>
<controls:AwesomeButton BindingContext="{Binding Source={x:Reference PostView}, Path=ParentBindingContext}" Command="{Binding OnTopicsPopupClicked}" CommandParameter="{Binding Topics}" Text="" TextColor="#09478e" BorderWidth="0" Margin="-5" BackgroundColor="Transparent" WidthRequest="36" FontSize="24"></controls:AwesomeButton>
回答2:
One solution is to add an ICommand property to your model and then in your viewModel, before adding the posts to the PostDataCollection, set the value of the new ICommand property to the desire command of the viewmodel. Something like:
public async Task<bool> GetPosts()
{
var response = await FetchPostsFromApi();
foreach(var post in response.posts)
{
post.NewCommand = RejectPostCommand;
PostDataCollection.Add(post)
}
return response.HasMoreItems;
}
Now bind the new ICommand property of the model in your Post View Template.
回答3:
So the problem was related to set binding in PostViewTemplate from ViewModel. I couldn't reference listView or PostsPage as binding source, because it was in another file and I couldn't go around it. I solved this by Moving PostViewTemplate XAML to PostsPage. SO now I can use
{Binding Path=BindingContext.RejectPostCommand, Source={x:Reference listView}}
and it works!
回答4:
You can create Commands in PostViewTemplate then bind it to ListView. Here is the example:
PostViewTemplate.xaml.cs:
public partial class PostViewTemplate : ContentView
{
public static BindableProperty RejectPostCommandProperty = BindableProperty.Create(
nameof(RejectPostCommand),
typeof(ICommand),
typeof(PostViewTemplate),
default(ICommand),
defaultBindingMode: BindingMode.OneWay);
public ICommand RejectPostCommand
{
get { return (ICommand)GetValue(RejectPostCommandProperty); }
set { SetValue(RejectPostCommandProperty, value); }
}
}
and in PostViewTemplate.xaml
<?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:Name="PostViewTemplate"
xmlns:model="clr-namespace:SOD_APP_V2.Model;assembly=SOD_APP_V2"
xmlns:controls="clr-namespace:SOD_APP_V2.Controls;assembly=SOD_APP_V2"
x:Class="SOD_APP_V2.View.PostViewTemplate">
<ContentView.Content>
....
<controls:AwesomeButton Command="{Binding RejectPostCommand, Source={x:Reference PostViewTemplate}}" CommandParameter="{Binding}" BackgroundColor="#d9534f" Grid.Row="6" Grid.Column="3" Text=" Reject" BorderColor="#d9534f"></controls:AwesomeButton>
....
</ContentView.Content>
</ContentView>
Notice I set Name of ContentView in PostViewTemplate.xaml
Now you just to have bind this new Command in ContentPage like this.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SOD_APP_V2"
xmlns:view="clr-namespace:SOD_APP_V2.View;assembly=SOD_APP_V2"
xmlns:viewModel="clr-namespace:SOD_APP_V2.ViewModel;assembly=SOD_APP_V2"
xmlns:controls="clr-namespace:SOD_APP_V2.Controls;assembly=SOD_APP_V2"
x:Class="SOD_APP_V2.PostsPage" x:Name="PostsPage">
<ContentPage.BindingContext>
<viewModel:PostDataViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
....
<controls:InfiniteListView x:Name="listView"
SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
IsLoadMoreItemsPossible="{Binding IsLoadMoreEnabled}"
LoadMoreInfiniteScrollCommand="{Binding LoadMorePostsCommand}"
IsEnabled="true"
IsBusy="{Binding IsBusy}"
HasUnevenRows="true"
ItemsSource="{Binding PostDataCollection}"
SeparatorVisibility="None">
<controls:InfiniteListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<view:PostViewTemplate RejectPostCommand="{Binding BindingContext.RejectPostCommand, Source={x:Reference listView}}"/>
</ViewCell>
</DataTemplate>
</controls:InfiniteListView.ItemTemplate>
</controls:InfiniteListView>
....
</ContentPage.Content>
</ContentPage>
来源:https://stackoverflow.com/questions/47156909/xamarin-forms-binding-command-from-view-model-to-button-in-list-view-data-templa