How send data (selectItem of listView) from xaml to ViewModel - xamarin.forms

I want get data selected in my viewModel
this is my xaml, but i donot know how solved my xaml, because this is bad, how use my behavior here in my xaml?
<ListView.Behaviors>
<behaviors:ItemTappedBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding SelectedTagChanged}" />
</behaviors:ItemTappedBehavior>
</ListView.Behaviors>
<ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
in my behindcode
public partial class ActorsView : ContentPage
{
public AccountsView()
{
InitializeComponent();
}
async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
Actor selectedItem = (Actor)e.Item;
Console.WriteLine("WORK"+ Actor.Name);
}
but i want to get in my viewModel, dont in my behind code
i've seen to solved is with commands, or behaviors
this is my viewModel:
public class ActorsViewModel : ViewModelBase
{
public List<Actor> actors { get; set; }
public AccountsViewModel(IActorManager actorManager)
: base()
{
Edit, i am using commands but i dont know how use the answer from John Livermore, i want to use and show the console Console.WriteLine("ROW");.
public class ItemTappedBehavior : Behavior<ListView>
{
public ICommand Command { get; set; }
protected override void OnAttachedTo(ListView bindable)
{
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
}
public Command SelectedTagChanged
{
get
{
return new Command(row =>
{
Console.WriteLine("ROW");
});
}
}
}

Since you had asked for a simpler Behavior for ListView selection changed. Please use the following:
Following class is inherited from BehaviorBase which takes care of BindingContext allocation.
On the OnAttachedTo override the ItemSelected event is hooked.
On the OnDetachingFrom override the ItemSelected event is unhooked.
Command bindable property Binds to the Command in ViewModel
In the event the e.SelectedItem is passed to the Command. (Change
it to whatever value you wish to pass)
public class SelectionChangedEventBehavior : BehaviorBase<ListView>
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(SelectionChangedEventBehavior), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttachedTo(BindableObject bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject.ItemSelected += AssociatedObject_ItemSelected;
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
AssociatedObject.ItemSelected -= AssociatedObject_ItemSelected;
}
private void AssociatedObject_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (Command != null)
{
Command.Execute(e.SelectedItem);
}
}
}
Usage
Xaml
<ListView ItemsSource="{Binding Collection}">
<ListView.Behaviors>
<local:SelectionChangedEventBehavior Command="{Binding SelectionCommand}" />
</ListView.Behaviors>
</ListView>
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public Command<object> SelectionCommand { get; set; }
public ViewModel()
{
SelectionCommand = new Command<object>(OnSelection);
}
private void OnSelection(object item)
{
var selectedActor = (item as Actor);
}
}
Hope this could be useful for you. If possible use the EventToCommandBehavior as whole, just because you can reuse it for any control and any event.

In your XAML do the following:
Set your <ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
to be <ListView ItemsSource="{Binding actors}" SelectedItem = "{Binding SelectedActor, Mode=TwoWay}>
In your view model which your current XAML is bound to, create two new variables as so:
private Actor selectedActor;
public Actor SelectedActor {
get {return selectedActor;}
set {
selectedActor = value;
if(selectedActor != null) {//set properties here if needed}
OnPropertyChanged("SelectedActor"); //or however you raise your events, this is a
//custom method i've made
}
Now, where we define our actors item source, we will also define what a selectedActor is as so:
//get your actors, loop over them and add them to the list of actors
var actorItem = new Actor();
actors.Add(actorItem);
//outside of list raise the property changed event on your actors list.
OnPropertyChanged("actors")
That is a the pattern that i've used at work to get my selected item. If you then need to do something with it you could define it within the SelectedActor variable. For us our app is a hybrid between old xamarin (.Android and .iOS) and .Forms, so we pass Actions from the native level to the view model (ex: loading the summary page for the selected item)

Related

Xamarin forms Back Button Navigation

I'm working on a Xamarin Forms app and am using the MVVM Design.
the issue is when am navigating to another page using
Shell.Current.GoToAsync()
I disable the button to prevent Creating Multiple Pages or DB Operations.
but if I want to go back, I re-enable the buttons in the VM constructor, but the constructor never gets called which means the buttons are still disabled.
I tried to append the // in the Page route to remove the stack thinking that when I go back it will create a new instance Page and VM, but that did not work.
so can anyone help me resolving this problem.
thanks in advance.
Update:
VM Code
public RegisterViewModel()
{
Debug.WriteLine("Class Constructor", Class_Name);
//in case if disabled
RegisterButtonEnabled = true;
RegisterCommand = new Command(RegisterButtonOnClick);
}
public ICommand RegisterCommand { get; }
private bool registerButtonEnabled = true;
public bool RegisterButtonEnabled
{
get => registerButtonEnabled;
set
{
registerButtonEnabled = value;
OnPropertyChanged();
}
}
private async void RegisterButtonOnClick()
{
RegisterButtonEnabled = false;
//More Code
//and then go to Register Page
await Shell.Current.GoToAsync(nameof(RegisterPage));
}
and my xaml
<Button
Command="{Binding RegisterCommand}"
Text="{xct:Translate Register}"
Style="{StaticResource ButtonStyle}"
IsEnabled="{Binding RegisterButtonEnabled,Mode=OneWay}"/>
I had create a default shell project. And find something about the viewmodel. You can add the onappear and the ondisappear method to the viewmodel. Such as:
ViewModel:
public void OnAppearing()
{
RegisterButtonEnabled = true;
}
public void OnDisAppearing()
{
RegisterButtonEnabled = false;
}
Page.cs
ItemsViewModel _viewModel;
public ItemsPage()
{
InitializeComponent();
BindingContext = _viewModel = new ItemsViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_viewModel.OnDisAppearing();
}

Updating content between tabs binded by Preference

I am using a tabbed app and I have a button on one tab, when the user clicks then it increments a Preference and I want to update its value on another tab that is Binded to a label. I tried to experiment with MVVM but I couldnt figure it out.
Page 1 view:
public partial class Page1View: ContentPage
{
public Page1View()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0)+1);
}
}
Page 2 Viewmodule:
public class Page2ViewModule : INotifyPropertyChanged
{
public int Total
{
get => Preferences.Get(nameof(Total), 0);
set
{
if (Preferences.Get(nameof(Total),0) == value)
return;
Preferences.Set(nameof(Total), value);
OnPropertyChanged(nameof(Total));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here's what I done with MessagingCenter. On the button click event I sent a message like this:
private void Button_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0)+1);
MessagingCenter.Send<Page>(this, "test");
}
In the Page2 view module I added a constructor that subscribes to message:
public Page2ViewModule()
{
MessagingCenter.Subscribe<Page>(this, "test", (p) =>
{
Total = Preferences.Get(nameof(Total), 0);
});
}
And that still doesn't work. But I'm not sure would this be the best way to do it? Or is there a more efficient way?
For Jason's question: Page2 already been created and the Subscribe method execute before you call Send, I test at my side, Page2 is been created and the Subscribe method execute after you call Send, so I think it works fine using MessagingCenter to notify each page when the Preference has been updated.
Tab one Page.
private void btn1_Clicked(object sender, EventArgs e)
{
Preferences.Set("Total", Preferences.Get("Total", 0) + 1);
label1.Text = Preferences.Get("Total", 0).ToString();
MessagingCenter.Send<Page>(this, "test");
}
Tab two page.
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Total}" />
</StackLayout>
</ContentPage.Content>
public partial class Tab2 : ContentPage
{
public Tab2()
{
InitializeComponent();
this.BindingContext = new Page2ViewModule();
}
}
public class Page2ViewModule : ViewModelBase
{
private int _Total;
public int Total
{
get { return _Total; }
set
{
_Total = value;
RaisePropertyChanged("Total");
}
}
public Page2ViewModule()
{
MessagingCenter.Subscribe<Page>(this, "test", (p) =>
{
Total = Preferences.Get("Total", 0);
});
}
}

Xamarin Forms Entry Return Type not working as expected

I tried setting the new feature in Xamarin Forms 3 which is ReturnType and I have set it to Next. My form has multiple fields and I want to make that the next Entry is focused when the Next button is pressed. However it just closes the keyboard. I did read the documents however I could not find the way to focus it to the next Entry. Can someone please guide?
Thanks in advance.
Those who want to know how I implemented it, it is as follows:
I created a behavior which will handle the OnAttachedTo and OnDetachingFrom so that I can handle the Completed event to move the focus. Now for that, I need a BindableProperty. I created the following code out of the logic:
public class NextEntryBehavior : Behavior<Entry>
{
public static readonly BindableProperty NextEntryProperty = BindableProperty.Create(nameof(NextEntry), typeof(Entry), typeof(Entry), defaultBindingMode: BindingMode.OneTime, defaultValue: null);
public Entry NextEntry
{
get => (Entry)GetValue(NextEntryProperty);
set => SetValue(NextEntryProperty, value);
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.Completed += Bindable_Completed;
base.OnAttachedTo(bindable);
}
private void Bindable_Completed(object sender, EventArgs e)
{
if (NextEntry != null)
{
NextEntry.Focus();
}
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.Completed -= Bindable_Completed;
base.OnDetachingFrom(bindable);
}
}
As you can see, there is a NextEntry property, we use it via XAML to focus on the desired entry field once the user marks it as complete using the Next button.
XAML:
<Entry ReturnType="Next">
<Entry.Behaviors>
<behaviors:NextEntryBehavior NextEntry="{x:Reference LastName}" />
</Entry.Behaviors>
</Entry>
In the above behavior, the LastName reference I used is the control to which the focus should go when the user taps on Next.
This way, it worked as expected and is reusable across the project.
the property ReturnType for Entry will only set graphically the Return Key in Keyboard to the specified type - Next in your case.
In order to set Focus for another Entry in the view you need to call Focus() from within the targeted Entry in the code-behind.
For Example:
private void txtUsername_OnCompleted(object sender, EventArgs e)
{
txtPassword.Focus();
}
if you're applying MVVM pattern. You will need a property in the Entry to Bind on for ViewModel property. One way to achieve this is by extending Entry control to add a bindable property called "IsActive" and create a Trigger that listens for changes on this property and calls Focus(), like below:
public class ExtendedEntry : Entry
{
public static readonly BindableProperty IsActiveProperty = BindableProperty.Create(
nameof(IsActive),
typeof(bool),
typeof(ExtendedEntry),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: false);
public bool IsActive
{
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
private Trigger _isActiveTriger;
private EntryIsActiveAction _activeAction;
public ExtendedEntry()
{
InitTriggers();
}
private void InitTriggers()
{
InitIsActiveTrigger();
}
private void InitIsActiveTrigger()
{
_activeAction = new EntryIsActiveAction();
_isActiveTriger = new Trigger(typeof(ExtendedEntry))
{
Value = false,
Property = IsActiveProperty,
EnterActions = { _activeAction },
ExitActions = { _activeAction }
};
Triggers.Add(_isActiveTriger);
}
}
public class EntryIsActiveAction : TriggerAction<ExtendedEntry>
{
protected override void Invoke(ExtendedEntry sender)
{
if (sender.IsActive)
{
sender.Focus();
return;
}
sender.Unfocus();
}
}
Example Usage:
Xaml page:
<Entry x:Name="txtPassword" IsActive="{Binding IsPasswordActive}" />
ViewModel:
private bool _isPasswordActive;
public bool IsPasswordActive
{
get => _isPasswordActive;
set
{
_isPasswordActive = value;
OnPropertyChanged();
}
}

Caliburn Action not firing

<ItemsControl DockPanel.Dock="Right" x:Name="Actions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="Action"
HorizontalAlignment="Right"
Content="{Binding Label}"
Margin="3" Width="30"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The above view binds with this viewmodel
public class DeploymentInputViewModel<T> : PropertyChangedBase
{
public BindableCollection<InputActionViewModel> Actions {get;set;}
}
I see my buttons. But when clicking it nothing happen.
The viewModels for InputActionViewModel:
public abstract class InputActionViewModel{
public InputActionViewModel()
{
}
public virtual Task Action()
{
return Task.FromResult<object>(null);
}
public string ActionToolTip { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
and also
public class InputCertificateActionViewModel : InputActionViewModel
{
[Import]
private IShell _shell;
[Import]
private IWindowsDialogs _dialogs;
private readonly IDeploymentSettingInputViewModel vm;
public InputCertificateActionViewModel(IDeploymentSettingInputViewModel vm)
{
this.vm = vm;
Label = "...";
ActionToolTip = "Pick a Certificate";
}
public bool IsManagementCertificate {get;set;}
public bool IsDeploymentCertificate { get; set; }
public async override Task Action()
{
if(IsManagementCertificate)
{
var subs = await _shell.IdentityModel.GetEnabledSubscriptionsAsync();
foreach(var sub in subs)
{
using (ManagementClient client = CloudContext.Clients.CreateManagementClient(sub.GetCredentials()))
{
var cert = _dialogs.SelectItemDialog("Select a certificate", "Pick one", true,
(await client.ManagementCertificates.ListAsync()).Select(c =>
new SelectItem(c.Thumbprint, Encoding.Default.GetString(c.PublicKey), c, (s) => c.Thumbprint.Contains(s))).ToArray())
.Tag as ManagementCertificateListResponse.SubscriptionCertificate;
this.vm.Value = cert.Thumbprint;
}
}
}else if(IsDeploymentCertificate)
{
}
}
}
I am adding actionViewModels by inserting directly into the observable code at startup.
haveActions.Actions.Add(DI.BuildUp(new InputCertificateActionViewModel(vm)
{
IsDeploymentCertificate = certAttribute.IsDeploymentCertificate,
IsManagementCertificate = certAttribute.IsManagementCertificate,
}));
haveActions is an instance of InputCertificateActionViewModel
Couldn't fit this all in a comment:
I can't have a peek at the Caliburn.Micro at the moment, but it might be something related to calling your method Action.
At a guess though, I'd say that by convention Caliburn.Micro expects to find a method that matches the Action<T> delegate to use for it's Actions, so your public virtual Task Action() won't be located and bound.
Have a quick check by defining a new method with a compatible signature, e.g public void MyMethod() and checking to see if it's located correctly and will function.
If that is the problem, you'll probably want to have a look at the IResult and Coroutines part of the Caliburn.Micro documentation, which looks like it will help you implement your desired behaviour.

Windows Phone 7 - Performance issues with "Filter as you write"-feature

i have some performance problems implementing a feature, where a listbox is realtime-filtered while user is typing the filter-string to a textbox. Feature i'm trying to create is similar to the call history search in WP7.
I created a simple project to test this and copy-pasted the important bits below. Basically i have a TextBox, where user is supposed to write a string that will be used to filter the data bound to a listbox. This filtering should happen in realtime, not after tapping any sort of filter-button etc.
ListBox is bound to a CollectionViewSource which uses a ObservableCollection as a source. When something is entered to the textbox, that value is instantly databound to a property in view model. View model property's setter fires up the filtering of CollectionViewSource, which updates the ListBox's contents.
In the actual project i'm doing, the ListBox can contain a hundred or so items.
Here's the related XAML:
<TextBox TextChanged="TextBox_TextChanged" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=Explicit}"></TextBox>
<ListBox ItemsSource="{Binding ItemsListCVS.View, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Prop1, Mode=TwoWay}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to trigger instant binding to ViewModel-property:
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
var textBox = sender as TextBox;
// Update the binding source
BindingExpression bindingExpr = textBox.GetBindingExpression(TextBox.TextProperty);
bindingExpr.UpdateSource();
}
ViewModel:
private ObservableCollection<AnItem> _itemsList = new ObservableCollection<AnItem>();
private CollectionViewSource _itemsListCvs = new CollectionViewSource();
public ObservableCollection<AnItem> ItemsList
{
get
{
return _itemsList;
}
set
{
_itemsList = value;
// Update bindings, no broadcast
RaisePropertyChanged(ItemsListPropertyName);
}
}
public string FilterString
{
get
{
return _filterString;
}
set
{
if (_filterString == value)
{
return;
}
_filterString = value;
// Update bindings, no broadcast
RaisePropertyChanged(FilterStringPropertyName);
this.Filter();
}
}
public CollectionViewSource ItemsListCVS
{
get
{
return _itemsListCvs;
}
set
{
if (_itemsListCvs == value)
{
return;
}
_itemsListCvs = value;
// Update bindings, no broadcast
RaisePropertyChanged(ItemListPropertyName);
}
}
public MainViewModel()
{
var items = Builder<AnItem>.CreateListOfSize(100).Build();
this.ItemsList = new ObservableCollection<AnItem>(items);
this.ItemsListCVS.Source = this.ItemsList;
}
private void Filter()
{
this.ItemsListCVS.View.Filter = r =>
{
if (r == null) return true;
return ((AnItem)r).Prop1.ToString().ToLowerInvariant().Contains(FilterString);
};
}
AnItem-class which is databound to the list:
public class AnItem
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
}
Question:
Everything is working okay, but there is a horrific lag between writing to the TextBox and updating of the ListBox.
Am i simply doing it wrong? If so, then how should i change my approach? I think that this is quite common requirement, so there's probably some nice solution for it.
Rather than rolling your own filter, could you use the AutoCompleteBox from the toolkit?
As an alternative, could you categorise the data and make it searchable via a LongListSelector?
Ultimately, if you've got poorly performing code you should use the Profiler to see where the actual bottleneck is. http://msdn.microsoft.com/en-us/library/hh202934(v=vs.92).aspx

Resources