I have a TableView in iOS and, in my ViewModel, I have a property to Selected Item in TableView, but I don't know how to bind the Selected Item for this property. How can I do that? My project is cross-platform. I have an Android project and an iOS project. In Android project, I did the bind:
<Mvx.MvxListView
android:id="#+id/lstViewTasks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:focusableInTouchMode="true"
android:choiceMode="multipleChoice"
local:MvxBind="ItemsSource Tasks; SelectedItem SelectedTask; ItemClick ShowTaskCommand"
local:MvxItemTemplate="#layout/projectmytasksitem" />
but I can't do a equivalent bind in iOS.
That's my TableViewController:
[Register("ProjectMyTasksViewc")]
public class ProjectMyTasksViews : MvxTableViewController<ProjectMyTasksViewModel>
{
//other things
var source = new MvxSimpleTableViewSource(TableView, ProjectMyTasksItem.Key, ProjectMyTasksItem.Key);
TableView.Source = source;
this.CreateBinding(source).To<ProjectMyTasksViewModel>(viewModel => viewModel.Tasks).Apply();
this.CreateBinding(source).For(s => s.SelectedItem).To<ProjectMyTasksViewModel>(viewModel => viewModel.SelectedTask).Apply();
this.CreateBinding(source).For(tableSource => tableSource.SelectionChangedCommand).To<ProjectMyTasksViewModel>(viewModel => viewModel.ShowTaskCommand).Apply();
}
Here is my ViewModel:
public class ProjectMyTasksViewModel : MvxViewModel
{
public Action ShowTaskCommandAction { get; set; }
private IList<Task> _tasks;
public IList<Task> Tasks
{
get { return _tasks; }
set { _tasks = value; RaisePropertyChanged(() => Tasks); }
}
private Task _selectedTask;
public Task SelectedTask
{
get { return _selectedTask; }
set { _selectedTask = value; RaisePropertyChanged(() => SelectedTask); }
}
private MvxCommand _showTaskCommand;
public MvxCommand ShowTaskCommand
{
get
{
_showTaskCommand = _showTaskCommand ?? (_showTaskCommand = new MvxCommand(ExecuteShowTaskCommand));
return _showTaskCommand;
}
}
private void ExecuteShowTaskCommand()
{
if (!SelectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
}
I believe it has to do with the timing of your ShowTaskCommand getting executed vs the set of SelectedTask. So if you commented out the code inside ExecuteShowTaskCommand and place a breakpoint inside ExecuteShowTaskCommand as well as the set of SelectedTask you would find that the ExecuteShowTaskCommand is running first and then the set of the SelectedTask.
Alternative implementation
To avoid the timing issue you can instead pass the selected task into your command as a parameter.
MvxCommand<Task> _showTaskCommand;
public MvxCommand<Task> ShowTaskCommand =>
_showTaskCommand ?? (_showTaskCommand = new MvxCommand<Task>(ExecuteShowTaskCommand));
private void ExecuteShowTaskCommand(Task selectedTask)
{
if (!selectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
Related
I'm a beginner on xamarin mvvm patter. Currently I'm trying to create a search bar that searches the word from a list of names. I tried to write some codes on comman function on my view model and bind it on the SearchCommand of search bar on view. But it didn't work. Here's my code
namespace HelloWorld.ViewModel
{
public class CustViewModel : INotifyPropertyChanged
{
private custmodel _custmodel;
public custmodel custmodel
{
get { return _custmodel; }
set
{
_custmodel = value;
NotifyPropertyChanged();
}
}
private string _message;
public string message
{
get { return _message; }
set
{
_message = value;
NotifyPropertyChanged();
}
}
private ObservableCollection<string> _list;
public ObservableCollection<string> Items
{
get
{
return _list;
}
set
{
_list = value;
NotifyPropertyChanged();
}
}
public Command SaveCommand
{
get
{
return new Command(() =>
{
message = "Your task : " + custmodel.name + ", " + custmodel.surname + " was successfully saved!";
});
}
}
private string _bar;
public string Bar
{
get { return _bar; }
set { _bar = value;
}
}
public Command SearchCommand
{
get
{
return new Command(() =>
{
string keyword = _bar;
IEnumerable<String> searchresult = _list.Where(name => name.Contains(keyword));
_list = new ObservableCollection<string>(searchresult);
NotifyPropertyChanged();
}
);
}
}
public CustViewModel()
{
custmodel = new custmodel
{
name = "Aasish",
surname = "Gurung",
email = "iamaaceez#yahoo.com"
};
_list = new ObservableCollection<string>();
_list.Add("Saurab");
_list.Add("Basanta");
_list.Add("Abhishek");
_list.Add("Surace");
_list.Add("Amir");
}
public event PropertyChangedEventHandler PropertyChanged;
//public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is my xaml file
<?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="HelloWorld.Styling"
BackgroundColor="AntiqueWhite" Title="Hello"
xmlns:converters="clr-namespace:HelloWorld.Converters; assembly=HelloWorld">
<StackLayout>
<SearchBar x:Name="MainSearchBar" Text="{Binding Bar}"
SearchCommand="{Binding SearchCommand}"/>
<ListView ItemsSource="{Binding Items}"/>
</StackLayout>
First, make sure you are setting your ContentPage's BindingContext to your CustViewModel.
Also, you should stop assigning and adding things to _list and instead assign and add things to your public Items property. Items is the one that will trigger the NotifyPropertyChanged() method when it has been assigned to.
So change your SearchCommand to this:
return new Command(() => {
string keyword = _bar;
IEnumerable<String> searchresult = _list.Where(name => name.Contains(keyword));
Items = new ObservableCollection<string>(searchresult);
//NotifyPropertyChanged(); //There is no reason to trigger NotifyPropertyChanged on this command each time the getter is run, I would image that this could cause an infinite loop
});
I am applying gesture feature to my Label control. I have used this link- http://arteksoftware.com/gesture-recognizers-with-xamarin-forms/
I was able to get the LongPressGestureRecognizer event when i performed long press on label control.This event is getting called in renderer file.
I want to perform some operation in my shared code on LongPressGestureRecognizer event. so how can I detect this event in my shared code ? How to handle event handler to get this longpress event in my shared code ?
In your custom control declare command:
public class TappedGrid : Grid
{
public static readonly BindableProperty TappedCommandProperty =
BindableProperty.Create(nameof(TappedCommand),
typeof(ICommand),
typeof(TappedGrid),
default(ICommand));
public ICommand TappedCommand
{
get { return (ICommand)GetValue(TappedCommandProperty); }
set { SetValue(TappedCommandProperty, value); }
}
public static readonly BindableProperty LongPressCommandProperty =
BindableProperty.Create(nameof(LongPressCommand),
typeof(ICommand),
typeof(TappedGrid),
default(ICommand));
public ICommand LongPressCommand
{
get { return (ICommand)GetValue(LongPressCommandProperty); }
set { SetValue(LongPressCommandProperty, value); }
}
}
And then raise this command from renderer:
public class TappedGridRenderer : ViewRenderer
{
UITapGestureRecognizer tapGesturesRecognizer;
UILongPressGestureRecognizer longPressGesturesRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
tapGesturesRecognizer = new UITapGestureRecognizer(() =>
{
var grid = (TappedGrid)Element;
if (grid.TappedCommand.CanExecute(Element.BindingContext))
{
grid.TappedCommand.Execute(Element.BindingContext);
}
});
longPressGesturesRecognizer = new UILongPressGestureRecognizer(() =>
{
var grid = (TappedGrid)Element;
if (longPressGesturesRecognizer.State == UIGestureRecognizerState.Ended &&
grid.LongPressCommand.CanExecute(Element.BindingContext))
{
grid.LongPressCommand.Execute(Element.BindingContext);
}
});
this.RemoveGestureRecognizer(tapGesturesRecognizer);
this.RemoveGestureRecognizer(longPressGesturesRecognizer);
this.AddGestureRecognizer(tapGesturesRecognizer);
this.AddGestureRecognizer(longPressGesturesRecognizer);
}
}
I have below MVVM cross command in my viewmodel. I want to call this based on condition from iOS. Is this possible?
Command
public IMvxCommand LoginCommand
{
get
{
return _loginCommand ?? (_loginCommand = new MvxCommand(async () => await ExecLoginClick()));
}
}
iOS Binding
var bindings = this.CreateBindingSet<LoginView, LoginViewModel>();
bindings.Bind(username).To(vm => vm.Email);
bindings.Bind(password).To(vm => vm.Password);
bindings.Bind(login_button).To(vm => vm.LoginCommand);
bindings.Bind(forgot_button).To(vm => vm.ForgotCommand);
bindings.Bind(register_button).To(vm => vm.GetSignUpCommand);
//bindings.Bind(btn_facebook).To(vm=>vm.)
bindings.Apply();
You can use CanExecute for this.
public IMvxCommand LoginCommand
{
get
{
return _loginCommand ??
(_loginCommand = new MvxAsyncCommand(ExecLoginClick, CanLogin));
}
}
private bool CanLogin()
{
if ( /*your condition*/)
{
return true;
}
return false;
}
private Task ExecLoginClick()
{
//...
}
And in every method, that affects your condition. You have to call
LoginCommand.RaiseCanExecuteChanged();
The Button is disabled or enabled based on the return value of CanExecute.
If you want to execute your command from your view, you should inherit from the generic MvxViewController<T> or MvxActivity<T> like.
public class LoginView : MvxViewController<LoginViewViewModel>
// or
public class LoginView : MvxActivity<LoginViewViewModel>
And then you can call
if(/*condition*/)
{
ViewModel.LoginCommand.Execute();
}
I have mvvmcross project, a ToggleButton with bindings for Checked, TextOn, and TextOff properties. I set the texts for those programmatically, I see in the setter that RaisePropertyChanged() is called, but the button text in UI stays the same, unless I click on it or change value of the property bound to "Checked". Changing Checked seems like a workaround, but ugly, is there a proper way?
FirstView.axml contains
<ToggleButton
android:id="#+id/myBtn"
local:MvxBind="TextOff MyBtnOFFLabel; TextOn MyBtnONLabel; Checked MyBtnChecked"
android:textOff="OFF"
android:textOn="ON"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:width="0dp" />
FirstViewModel.cs contains
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set { myBtnOFFLabel = value; RaisePropertyChanged(() => MyBtnOFFLabel); }
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set { myBtnONLabel = value; RaisePropertyChanged(() => MyBtnONLabel); }
}
public FirstViewModel()
{
Global.EventSomethingChanged += Handler_SomethingChanged;
}
void Handler_SomethingChanged(object sender, EventArgs e)
{
UpdateUI();
}
private void UpdateUI()
{
MyBtnOFFLabel = "new OFF";
MyBtnONLabel = "new ON";
}
SecondViewModel.cs contains
Global.FireEventSomethingChanged();
as expected, UpdateUI() is called in FirstViewModel.cs and it updates the label properties for myBtn (confirmed in debug mode), but when I close SecondViewModel in emulator I see the old label remaining in first view UI. If I click on that button, it switches to showing correct labels.
I believe you needed to RaisePropertyChanged on your MyBtnChecked. In your comment above you mention that adding it to UpdateUI() causes it to redraw properly. That's not a "work around" per se as you need to put that somewhere. You could also do this:
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set {
myBtnOFFLabel = value;
RaisePropertyChanged(() => MyBtnOFFLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set {
myBtnONLabel = value;
RaisePropertyChanged(() => MyBtnONLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
As it sounds like your MyBtnChecked is dependent on changes to MyBtnOFFLabel and MyBtnONLabel. Otherwise you need to be firing this RaisePropertyChanged somewhere else.. For example I would have expected to see a property in your example code for MyBtnChecked which would do this. Then in your view model when MyBtnChecked is changed it would fire the RaisePropertyChanged event to mark it as checked or not when your ViewModel bool property is changed.
I'm trying to remove an item from a ListBox. The command is properly fired and the item is correctly removed from the database but the list is not refreshed.
Here is the ViewModel. I'm using MVVM Light 4.1
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures);
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
if (Measures.Remove(measure))
{
RaisePropertyChanged(() => Measures);
}
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
Thanks for the help.
PS: I know there are similar questions but none of the accepted answers worked for me :(
EDIT 1
This is the code I use in the XAML page to bind the ListBox to the list of items:
<ListBox Grid.Row="1" DataContext="{Binding Path=Measures}" ItemsSource="{Binding}" />
here is the binding of the ViewModel to the main container
<Grid DataContext="{Binding Source={StaticResource Locator}, Path=ViewAll}" x:Name="LayoutRoot" />
EDIT 2
This is the full code of the ViewModel
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
LoadMeasures();
Messenger.Default.Register<PropertyChangedMessage<ObservableCollection<Measure>>>(this, message => LoadMeasures());
}
private void LoadMeasures()
{
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures.OrderByDescending(m => m.MeasureDate).ThenByDescending(m => m.Id).Take(20));
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
Measures.Remove(measure);
RaisePropertyChanged("LastMeasure", null, measure, true);
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
I don't see anything obviously wrong. All I can suggest is to try simplifying your ListBox to just this:
<ListBox Grid.Row="1" ItemsSource="{Binding Path=Measures}" />
And remove the code that calls RaisePropertyChanged(() => Measures); (since it should not be needed).
If neither of those work, I would test to see what happens if you completely reset your Measures property, as in:
private void DeleteMeasure(int measureId)
{
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
var measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
Measures = repository.Measures;
}
If that causes a successful refresh of the ListBox, it would imply that something is going on with the ObservableCollection.