I'm using Event to Command Behavior found here to implement a command in the ItemTapped event of a ListView. I copied the EventToCommandBehavior and BehaviorBase classes to my project.
Here is my View
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AppVentas.Behaviors"
mc:Ignorable="d"
x:Class="AppVentas.Views.UsuariosView">
<ContentPage.Content>
<ListView HasUnevenRows="True"
ItemsSource="{Binding Usuarios}"
ItemTapped="ListView_ItemTapped">
<ListView.Behaviors>
<local:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding OpenChatCommand}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Nombre}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
And my ViewModel
public class UsuariosViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Usuarios> Usuarios { get; set; }
public ICommand OpenChatCommand { get; set; }
private UsuarioController usuarioController = new UsuarioController();
public UsuariosViewModel()
{
Usuarios = new ObservableCollection<Usuarios>(usuarioController.Get().Where(i => i.Token == null));
OpenChatCommand = new Command<Usuarios>(OpenChat);
}
void OpenChat(Usuarios usuario)
{
//trying to do something
}
}
The problem is that OpenChatCommand never gets executed, the method OnEvent of the EventToCommandBehavior class do gets executed but the line Command.Execute (resolvedParameter); just doesn't do anything.
I'm using the PropertyChanged.Fody package if that's of any use.
Any help is appreciated, thanks.
From Reusable EventToCommandBehavior, I suggest you can use Converter in your code, I create simple that you can take a look:
<ContentPage
x:Class="Listviewsample.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converter="clr-namespace:Listviewsample"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:local="clr-namespace:Listviewsample.Behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ContentPage.Resources>
<converter:TappedItemConverter x:Key="converter1" />
</ContentPage.Resources>
<StackLayout>
<!-- Place new controls here -->
<ListView
x:Name="listview1"
HasUnevenRows="True"
ItemsSource="{Binding images}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding title}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<local:EventToCommandBehavior
Command="{Binding command1}"
Converter="{StaticResource converter1}"
EventName="ItemTapped" />
</ListView.Behaviors>
</ListView>
</StackLayout>
public partial class MainPage : ContentPage
{
public ObservableCollection<imagemodel> images { get; set; }
public Command command1 { get; set; }
public MainPage()
{
InitializeComponent();
images = new ObservableCollection<imagemodel>()
{
new imagemodel(){title="image 1"},
new imagemodel(){title="image 2"},
new imagemodel(){title="image 3"}
};
command1 = new Command<imagemodel>(commandpage);
this.BindingContext = this;
}
private void commandpage(imagemodel m)
{
Console.WriteLine("the image model title is {0}",m.title.ToString());
}
}
public class imagemodel
{
public string title { get; set; }
}
The BehaviorBase.cs:
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null)
{
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom(T bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
The EventToCommandBehavior.cs:
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent(EventName);
}
protected override void OnDetachingFrom(View bindable)
{
DeregisterEvent(EventName);
base.OnDetachingFrom(bindable);
}
void RegisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
}
void DeregisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (eventHandler == null)
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent(object sender, object eventArgs)
{
if (Command == null)
{
return;
}
object resolvedParameter;
if (CommandParameter != null)
{
resolvedParameter = CommandParameter;
}
else if (Converter != null)
{
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
}
else
{
resolvedParameter = eventArgs;
}
if (Command.CanExecute(resolvedParameter))
{
Command.Execute(resolvedParameter);
}
}
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null)
{
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent(oldEventName);
behavior.RegisterEvent(newEventName);
}
}
The Converter.cs,The Command property of the behavior is data bound to the command1 property of the associated ViewModel, while the Converter property is set to the TappedItemConverter instance, which returns the Tapped item of the ListView from the ItemTappedEventArgs.
public class TappedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as ItemTappedEventArgs;
return eventArgs.Item;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Here is the sample at github, you can take a look:
https://github.com/CherryBu/Eventtocommand
Assuming your bindingcontext is correctly set to an instance of UsuariosViewModel, the issue I see here is that you are not passing the command parameter. Your command accepts a Usuarios, but you need to pass that in via the CommandParameter property on the EventToCommandBehavior.
I also notice you have ListView_ItemTapped defined, but I'm not sure what you're doing in that method. It shouldn't be required to make the command work, but maybe you're using it for something else.
Related
I have this label in Xamarin form:
<Label HorizontalOptions="Start"/>
But i want the value of HorizontalOptions become changeable bases on my input into a string variable (string getH) in the backend code.
I tried using binding method, but it does not work.
<Label HorizontalOptions="{Binding
getH}"/> (XAML code)
getH = "End"; (C# code)
Is there any binding method that could work in this case?
Yes, if you want to bind string variabke, you could use converter to achieve this.
I created a simple demo , you can refer to it.
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
public ICommand BtnResetClickedCommand { private set; get; }
public MyViewModel() {
GetH = "End";
BtnResetClickedCommand = new Command(ResetMethod);
}
private void ResetMethod(object obj)
{
GetH = "Start";
}
string _getH;
public string GetH
{
set { SetProperty(ref _getH, value); }
get { return _getH; }
}
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;
}
MyTextAlignmentConverter.cs
public class MyTextAlignmentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
string valueAsString = value.ToString();
switch (valueAsString)
{
case ("EndAndExpand"):
{
return LayoutOptions.EndAndExpand;
}
case ("StartAndExpand"):
{
return LayoutOptions.StartAndExpand;
}
case ("Center"):
{
return LayoutOptions.Center;
}
case ("End"):
{
return LayoutOptions.End;
}
case ("Start"):
{
return LayoutOptions.Start;
}
default:
{
return LayoutOptions.StartAndExpand;
}
}
}
else
{
return LayoutOptions.Center;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Usage:
<?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:testbottomsheet="clr-namespace:TestBottomSheet"
x:Class="TestBottomSheet.OptionPage">
<ContentPage.Resources>
<ResourceDictionary >
<testbottomsheet:MyTextAlignmentConverter x:Key="mTextAlignmentConverter">
</testbottomsheet:MyTextAlignmentConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.BindingContext>
<testbottomsheet:MyViewModel></testbottomsheet:MyViewModel>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand">
<Label Text="Welcome to Xamarin.Forms!" BackgroundColor="Yellow"
VerticalOptions="CenterAndExpand" HorizontalOptions="{Binding GetH,Converter={StaticResource mTextAlignmentConverter}}"
/>
<Button Text="reset" Command="{Binding BtnResetClickedCommand}}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Note:
In above code ,I implemented interface INotifyPropertyChanged for viewmodel MyViewModel, and added a reset button.When I click the button, we can also change the binded value for variable GetH and refresh the UI.
The type of HorizontalOptions is not string and hence you can't bind to a string. Its of type LayoutOptions and you should bind it to a property of type LayoutOptions like below,
public LayoutOptions GetH { get; set; } = LayoutOptions.Center;
and then on the Xaml,
<Label HorizontalOptions="{Binding GetH}" />
or else if you wish to bind to a string for some reason, then you can have a converter. There's already a solution for this here.
I have this XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
...
x:Class="MyProj.LoginPageView"
local:ViewModelLocator.AutoWireViewModel="True">
...cut for brevity...
<Entry MinimumWidthRequest="60" Grid.Row="0" Grid.Column="2" VerticalOptions="Center" Text="{Binding Username, Mode=TwoWay}"/>
<Entry Grid.Row="1" Grid.Column="2" VerticalOptions="Center" Text="{Binding Password, Mode=TwoWay}"/>
<Button Text="Login" Command="{Binding LoginCommand}"/>
<Button Text="Register" Command="{Binding RegisterCommand}"/>
Attached using autowiring to this viewmodel:
public class LoginPageViewModel: BindableObject
{
public ICommand LoginCommand { get; set; }
public ICommand RegisterCommand { get; set; }
private string username;
private string password;
public string Username { get { return username; } set { username = value; OnPropertyChanged("Username"); } }
public string Password { get { return password; } set { password = value; OnPropertyChanged("Password"); } }
public LoginPageViewModel()
{
LoginCommand = new Command(() =>
Login());
RegisterCommand = new Command(() =>
Register());
}
private void Login()
{
}
private void Register()
{
Console.WriteLine("sdfsd");
}
}
The command bindings are working fine and the Register and Login methods get called when I click the button. In the autowiring the binding context of the view is set to the viewmodel as follows:
static ViewModelLocator()
{
_container = new TinyIoCContainer();
//register viewmodels
_container.Register<LoginPageViewModel>();
}
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable)
{
return (bool)bindable.GetValue(ViewModelLocator.AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(BindableObject bindable, bool value)
{
bindable.SetValue(ViewModelLocator.AutoWireViewModelProperty, value);
}
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
if (view == null)
{
return;
}
var viewType = view.GetType();
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = string.Format(
CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null)
{
return;
}
var viewModel = _container.Resolve(viewModelType);
view.BindingContext = viewModel;
}
When I click register in debug mode the register method gets called which means the command binding has been connected but the value I put in the Entry fields for username and password aren't connected to the corresponding properties on the VM and the properties just have null values.
I am working on datagrid sample based on this one.
<dg:DataGrid x:Name="datagrid" ItemsSource="{Binding Teams}" SelectionEnabled="True" SelectedItem="{Binding SelectedTeam}" ActiveRowColor="Red"
RowHeight="70" HeaderHeight="50" BorderColor="#CCCCCC" HeaderBackground="#E0E6F8" Focused="Datagrid_Focused"
PullToRefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}"
>
<x:Arguments>
<ListViewCachingStrategy>RetainElement</ListViewCachingStrategy>
</x:Arguments>
<dg:DataGrid.HeaderFontSize>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Tablet>15</OnIdiom.Tablet>
<OnIdiom.Phone>12</OnIdiom.Phone>
</OnIdiom>
</dg:DataGrid.HeaderFontSize>
<dg:DataGrid.Columns>
<dg:DataGridColumn Title="Logo" PropertyName="Logo" Width="100" SortingEnabled="False">
<dg:DataGridColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding}" HorizontalOptions="Center" VerticalOptions="Center" Aspect="AspectFit" HeightRequest="60" />
</DataTemplate>
</dg:DataGridColumn.CellTemplate>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Team" PropertyName="Name" Width="2*">
<dg:DataGridColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="gridtest" BindingContext="{Binding .}">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Grid.GestureRecognizers>
<Label x:Name="label1" Text="{Binding Name,Mode=TwoWay}" />
<Editor x:Name="editorTest" Completed="Editor_Completed" BindingContext="{Binding .}" Focused="Editor_Focused">
</Editor>
</Grid>
</DataTemplate>
</dg:DataGridColumn.CellTemplate>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Win" PropertyName="Win" Width="0.95*"/>
<dg:DataGridColumn Title="Loose" PropertyName="Loose" Width="1*"/>
<dg:DataGridColumn PropertyName="Home">
<dg:DataGridColumn.FormattedTitle>
<FormattedString>
<Span Text="Home" ForegroundColor="Black" FontSize="13" FontAttributes="Bold"/>
<Span Text=" (win-loose)" ForegroundColor="#333333" FontSize="11" />
</FormattedString>
</dg:DataGridColumn.FormattedTitle>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Percentage" PropertyName="Percentage" StringFormat="{}{0:0.00}" />
</dg:DataGrid.Columns>
</dg:DataGrid>
I have a Team model, the name what i entered in editor should be bind in that model property. In static cases it is working. How to acheive in dynamic case like,
I have add button . In add button only i am adding item to the collection Teams,
private void AddButton_Clicked(object sender, EventArgs e)
{
viewModel.Teams.Add(new Models.Team()
{
Win = 73,
Name = "",
Loose = 9,
Percentage = 0.89,
Conf = "46-6",
Div = "15-1",
Home = "39-2",
Road = "34-7",
Last10 = "8-2",
Streak = "W 4",
Logo = "gsw.png"
});
}
When editor is opened, datagrid selectedItem event is not working.and the text is not binding to the model property Name.
On clicking the Save Button, I need updated collection.
How to acheive this scenario?
Since you had used MVVM , you should handle all the logic in ViewModel. You could bind the Text of Editor in model.
<Editor Text="{Binding editorText,Mode=TwoWay}"... />
in model
define a new property
public class Team : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
string editorText;
public string EditorText
{
get
{
return editorText;
}
set
{
if(value!=null)
{
editorText = value;
OnPropertyChanged("EditorText");
}
}
}
//...
}
When editor is opened, datagrid selectedItem event is not working.
This maybe a issue of the plugin . You could set the SelectItem when the editor is focused .
Add the following class in your project
using System;
using Xamarin.Forms;
namespace xxx
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo (T bindable)
{
base.OnAttachedTo (bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null) {
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom (T bindable)
{
base.OnDetachingFrom (bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged (object sender, EventArgs e)
{
OnBindingContextChanged ();
}
protected override void OnBindingContextChanged ()
{
base.OnBindingContextChanged ();
BindingContext = AssociatedObject.BindingContext;
}
}
}
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace xxx
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName {
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
public ICommand Command {
get { return (ICommand)GetValue (CommandProperty); }
set { SetValue (CommandProperty, value); }
}
public object CommandParameter {
get { return GetValue (CommandParameterProperty); }
set { SetValue (CommandParameterProperty, value); }
}
public IValueConverter Converter {
get { return (IValueConverter)GetValue (InputConverterProperty); }
set { SetValue (InputConverterProperty, value); }
}
protected override void OnAttachedTo (View bindable)
{
base.OnAttachedTo (bindable);
RegisterEvent (EventName);
}
protected override void OnDetachingFrom (View bindable)
{
DeregisterEvent (EventName);
base.OnDetachingFrom (bindable);
}
void RegisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler (AssociatedObject, eventHandler);
}
void DeregisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
if (eventHandler == null) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler (AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent (object sender, object eventArgs)
{
if (Command == null) {
return;
}
object resolvedParameter;
if (CommandParameter != null) {
resolvedParameter = CommandParameter;
} else if (Converter != null) {
resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
} else {
resolvedParameter = eventArgs;
}
if (Command.CanExecute (resolvedParameter)) {
Command.Execute (resolvedParameter);
}
}
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null) {
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent (oldEventName);
behavior.RegisterEvent (newEventName);
}
}
}
in xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:xxx"
mc:Ignorable="d"
x:Name="page" // set name here
x:Class="xxx.MainPage">
<Editor.Behaviors>
<local:EventToCommandBehavior EventName="Focused" Command="{Binding Source={x:Reference page},Path=BindingContext.xxxCommand}" CommandParameter="{Binding }" />
</Editor.Behaviors>
in ViewModel
xxxCommand = new Command((model)=>{
var item = model as Team;
SelectedTeam = item;
// ...
});
I created a custom control which has an Entry and a Label inside a StackControl. Then I exposed the IsEnabled property of the Entry thorough a bindable property called IsEntryEnabled which I wanted to bind to a bool property of my VM. But it never triggers. Any idea what am I doing wrong here?
Custom control - Xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:VMSTablet.Effects;assembly=VMSTablet"
x:Class="VMSTablet.Controls.StandardFormEntry">
<StackLayout >
<Label Text="{Binding LabelText}" Style="{StaticResource DefaultLabelStyle}" TextColor="{StaticResource DarkGreenColor}"/>
<Entry x:Name="CustomEntry" Text="{Binding Text}"
IsEnabled="{Binding IsEntryEnabled}"
Keyboard="{Binding Keyboard}"
Behaviors="{Binding Behaviors}"
TextColor="{StaticResource DarkGreenColor}"
Placeholder="{Binding Placeholder}" Style="{StaticResource DefaultEntryStyle}" >
<Entry.Effects>
<effects:EntryBarColorEffect/>
</Entry.Effects>
</Entry>
</StackLayout>
</ContentView>
Custom control - Code Behind
namespace VMST.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StandardFormEntry
{
public event EventHandler OnTextChanged;
public event EventHandler OnUnfocused;
public event EventHandler OnFocused;
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create("Placeholder", typeof(string), typeof(StandardFormEntry), string.Empty);
public static readonly BindableProperty LabelTextProperty =
BindableProperty.Create("LabelText", typeof(string), typeof(StandardFormEntry), string.Empty);
public static readonly BindableProperty EntryTextProperty =
BindableProperty.Create("EntryText", typeof(string), typeof(StandardFormEntry), string.Empty);
public static BindableProperty IsEntryEnabledProperty =
BindableProperty.Create("IsEntryEnabled", typeof(bool), typeof(StandardFormEntry), true);
public static readonly BindableProperty KeyboardProperty =
BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(StandardFormEntry), Xamarin.Forms.Keyboard.Default);
public static readonly BindableProperty BehaviorsProperty =
BindableProperty.Create(nameof(Behaviors), typeof(IList<Behavior>), typeof(StandardFormEntry));
public string LabelText
{
set
{
SetValue(LabelTextProperty, value);
}
get => (string)GetValue(LabelTextProperty);
}
public string EntryText
{
set => SetValue(EntryTextProperty, value);
get => (string)GetValue(EntryTextProperty);
}
public string Placeholder
{
set => SetValue(PlaceholderProperty, value);
get => (string)GetValue(PlaceholderProperty);
}
public bool IsEntryEnabled
{
set
{
SetValue(IsEntryEnabledProperty, value);
}
get => (bool)GetValue(IsEntryEnabledProperty);
}
public Keyboard Keyboard
{
set => SetValue(KeyboardProperty, value);
get => (Keyboard)GetValue(KeyboardProperty);
}
public IList<Behavior> Behaviors
{
set => SetValue(BehaviorsProperty, value);
get => (IList<Behavior>)GetValue(BehaviorsProperty);
}
public StandardFormEntry()
{
InitializeComponent();
BindingContext = this;
CustomEntry.BindingContext = this;
PropertyChanged += StandardFormEntry_PropertyChanged;
CustomEntry.Unfocused += CustomEntry_Unfocused;
CustomEntry.TextChanged += CustomEntry_TextChanged;
CustomEntry.Focused += CustomEntry_Focused;
}
private void StandardFormEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == IsEntryEnabledProperty.PropertyName)
{
**//This never happens!**
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
}
private void CustomEntry_Focused(object sender, FocusEventArgs e)
{
OnFocused?.Invoke(sender, e);
}
private void CustomEntry_TextChanged(object sender, TextChangedEventArgs e)
{
OnTextChanged?.Invoke(sender, e);
}
private void CustomEntry_Unfocused(object sender, FocusEventArgs e)
{
OnUnfocused?.Invoke(sender, e);
}
}
}
View Model
I'm trying to trigger the IsEntryEnabled property inside the EditForm() method below. But it doesn't work.
public class PassRegistrationPageViewModel : ViewModelBase
{
public DelegateCommand AddressCommand { get; set; }
public DelegateCommand EditCommand { get; set; }
public DelegateCommand ConfirmCommand { get; set; }
public PassRegistrationPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Page Title";
AddressCommand = new DelegateCommand(ShowBuildings);
EditCommand = new DelegateCommand(EditForm);
ConfirmCommand = new DelegateCommand(ConfirmPass);
//IsQrVisible = false;
}
private bool _isQrVisible;
public bool IsQrVisible
{
get { return _isQrVisible; }
set {
SetProperty(ref _isQrVisible, value);
}
}
private bool _isEditingEnabled;
public bool IsEditingEnabled //Bound to the view below
{
get { return _isEditingEnabled; }
set { SetProperty(ref _isEditingEnabled, value); }
}
private string _text;
public string Text
{
get { return _text; }
set
{
SetProperty(ref _text, value);
}
}
private async void ShowBuildings()
{
await NavigationService.NavigateAsync(nameof(BuildingListPage));
}
public void EditForm()
{
IsEditingEnabled = IsEditingEnabled ? false : true;
}
public void ConfirmPass()
{
}
}
View
I bind the IsEditingEnabled property to IsEntryEnabled property in my custom control which is supposed to trigger the IsEnabled property in the Entry inside. But it never triggers.
<controls:StandardFormEntry IsEntryEnabled="{Binding IsEditingEnabled}" EntryText="{Binding Text}" LabelText="Name" Grid.Column="0" Grid.Row="3"/>
In your custom control, you need to give a name to your content view and refer the name to the source of the binding property, like below:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:VMSTablet.Effects;assembly=VMSTablet"
x:Class="VMSTablet.Controls.StandardFormEntry"
x:Name="CustomEntryControl">
<StackLayout >
<Label Text="{Binding LabelText}" Style="{StaticResource DefaultLabelStyle}" TextColor="{StaticResource DarkGreenColor}"/>
<Entry x:Name="CustomEntry" Text="{Binding Text}"
IsEnabled="{Binding IsEntryEnabled, Source={x:Reference CustomEntryControl}}"
Keyboard="{Binding Keyboard}"
Behaviors="{Binding Behaviors}"
TextColor="{StaticResource DarkGreenColor}"
Placeholder="{Binding Placeholder}" Style="{StaticResource DefaultEntryStyle}" >
<Entry.Effects>
<effects:EntryBarColorEffect/>
</Entry.Effects>
</Entry>
</StackLayout>
So i wanted to use commanding inside my ViewModel so i can interact with my listview. I looked at the forms example of EventToCommandBehavior. I tried to replicate the code inside my project but for some reason i can't get it to work.
What i got is this:
BehaviorBase.cs inside Sogeti.Core.Behaviors folder
using System;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null)
{
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom(T bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
}
EventToCommandBehavior.cs inside Sogeti.Core.Behaviors folder
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent(EventName);
}
protected override void OnDetachingFrom(View bindable)
{
DeregisterEvent(EventName);
base.OnDetachingFrom(bindable);
}
void RegisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
}
void DeregisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (eventHandler == null)
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent(object sender, object eventArgs)
{
if (Command == null)
{
return;
}
object resolvedParameter;
if (CommandParameter != null)
{
resolvedParameter = CommandParameter;
}
else if (Converter != null)
{
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
}
else
{
resolvedParameter = eventArgs;
}
if (Command.CanExecute(resolvedParameter))
{
Command.Execute(resolvedParameter);
}
}
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null)
{
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent(oldEventName);
behavior.RegisterEvent(newEventName);
}
}
}
SelectedItemEventArgsToSelectedItemConverter.cs inside Sogeti.Core.Converters folder
using System;
using System.Globalization;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class SelectedItemEventArgsToSelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as SelectedItemChangedEventArgs;
return eventArgs.SelectedItem;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
MyViewModel inside Sogeti.Core.ViewModel folder
using System;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Sogeti.Core
{
public class SogetistDetailsViewModel : SimpleViewModel
{
private Sogetist sogetist;
public ICommand ViewSelectedCommand { get; private set; }
public string FullName
{
get
{
return sogetist.Name + " " + sogetist.LastName;
}
}
public string Introduction
{
get
{
return sogetist.Introduction;
}
set
{
if (sogetist.Introduction != value)
{
sogetist.Introduction = value;
RaisePropertyChanged(() => Introduction);
}
}
}
public string Function
{
get
{
return sogetist.Function.Name;
}
set
{
if (value != sogetist.Function.Name)
{
sogetist.Function.Name = value;
RaisePropertyChanged(() => Function);
}
}
}
public string Skills
{
get
{
List<string> skills = sogetist.Skill.Select(x => x.Name).ToList();
return string.Join(", ", skills);
}
}
public string Image
{
get
{
return sogetist.Image;
}
set
{
if (value != sogetist.Image)
{
sogetist.Image = value;
RaisePropertyChanged(() => Image);
}
}
}
public SogetistDetailsViewModel() : this(new Sogetist())
{
}
public SogetistDetailsViewModel(Sogetist sogetist)
{
this.sogetist = sogetist;
Image = this.sogetist.Image;
ViewSelectedCommand = new Command<Sogetist>(OnViewSelected);
}
void OnViewSelected(Sogetist obj)
{
String a = obj.Name;
}
}
}
MainPage.Xaml inside Sogeti namespace
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="Sogeti.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:core="clr-namespace:Sogeti.Core;assembly=Sogeti.Core"
Title="Sogetist list">
<ContentPage.Resources>
<ResourceDictionary>
<core:SelectedItemEventArgsToSelectedItemConverter x:Key="SelectedItemConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView x:Name="listView"
CachingStrategy="RecycleElement"
Footer="{Binding Count}"
IsPullToRefreshEnabled="True"
ItemsSource="{Binding .}">
<ListView.Behaviors>
<core:EventToCommandBehavior EventName="ItemSelected" Command="{Binding ViewSelectedCommand}" Converter="{StaticResource SelectedItemConverter}" />
</ListView.Behaviors>
<ListView.FooterTemplate>
<DataTemplate>
<ContentView BackgroundColor="#FF4411" Padding="0,5">
<Label FontSize="Micro"
HorizontalTextAlignment="Center"
Text="{Binding .,
StringFormat='{0} Sogetists'}"
TextColor="White"
VerticalTextAlignment="Center">
<Label.Triggers>
<DataTrigger Binding="{Binding .}"
TargetType="Label"
Value="1">
<Setter Property="Text" Value="{Binding ., StringFormat='{0} Sogetist'}" />
</DataTrigger>
</Label.Triggers>
</Label>
</ContentView>
</DataTemplate>
</ListView.FooterTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Detail="{Binding Function}"
ImageSource="{Binding Image}"
Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
MainPage.Xaml.cs
using Xamarin.Forms;
using Sogeti.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace Sogeti
{
public partial class MainPage : ContentPage
{
private readonly BackendlessHandler backendless = new BackendlessHandler();
public ObservableCollection<SogetistDetailsViewModel> Sogetists { get; private set; }
public MainPage()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
base.OnAppearing();
if (Sogetists == null)
{
await LoadSogetistsAsync();
BindingContext = Sogetists;
}
}
private async Task LoadSogetistsAsync()
{
IsBusy = true;
try
{
var sogetistDetailsViewModelList = (await backendless.GetAllSogetistsAsync()).OrderBy(x => x.Name).Select(x => new SogetistDetailsViewModel(x));
Sogetists = new ObservableCollection<SogetistDetailsViewModel>(sogetistDetailsViewModelList);
}
catch (Exception ex)
{
await this.DisplayAlert("Error", "Failed to download sogetists: " + ex.Message, "OK");
}
finally
{
IsBusy = false;
}
}
}
}
Your problem is the binding to command you defined.
There is no ViewModel behind your MainPage, instead, currently you just write the logic in the View itself. I would suggest to use pure MVVM approach in order to simplify your solution and for this you need to:
Create a MainViewModel
Move all the logic from MainPage in to MainViewModel
Set the BindingContext of the MainPage to MainViewModel
So your MainViewModel will contain a list of ObservalbeCollection< SogetistDetailsViewModel> & your command with SogetistDetailsViewModel parameter defined only once.
I suggest to use this plugin. (here the source)
You can find a sample here, in my GitHub repository.
This is the 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:TestListViewMultiSelectItems"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
x:Class="TestListViewMultiSelectItems.TestListViewMultiSelectItemsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:SelectedItemEventArgsToSelectedItemConverter x:Key="SelectedItemConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Padding="20,20,20,20">
<Label Text = "{Binding SelectedItemsCounter, StringFormat='SelectedItems\' Counter {0}'}" HorizontalTextAlignment = "Center"/>
<ListView ItemsSource="{Binding Items}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemTapped">
<behaviors:InvokeCommandAction Command="{Binding ItemTappedCommand}"/>
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding DisplayName}" TextColor = "Fuchsia" HorizontalOptions = "StartAndExpand"/>
<BoxView Color="Fuchsia" IsVisible="{Binding Selected}" HorizontalOptions = "End"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
In this sample I use an ItemTappedCommand.
In my ViewModel
ItemTappedCommand = new Command((object model) => {
if (model != null && model is ItemTappedEventArgs) {
if (!((Model)((ItemTappedEventArgs)model).Item).Selected)
SelectedItemsCounter++;
else
SelectedItemsCounter--;
((Model)((ItemTappedEventArgs)model).Item).Selected = !((Model)((ItemTappedEventArgs)model).Item).Selected;
}
});