Xamarin Forms - Binding Image Scale - mvvm

I'm using Xamarin Forms with an MVVM pattern. I want to scale an image down when the tap gesture command is triggered, wait a few milliseconds and scale it back to full size to give a button press effect.
Here is my XAML code:
<Image
x:Name="RefreshImage"
WidthRequest="24"
Scale="{Binding ImageScale, Mode=TwoWay}"
Source="{local:ImageResource MyProject.Resources.refresh.png}"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RefreshTapCommand, Mode=TwoWay}" CommandParameter="RefreshImage" />
</Image.GestureRecognizers>
</Image>
Here is my View Model:
public class AlertListViewModel : BaseViewModel
{
public ICommand RefreshTapCommand { get; private set; }
public double ImageScale { get; set; }
public AlertListViewModel()
{
RefreshList();
items.CollectionChanged += this.OnCollectionChanged;
RefreshTapCommand = new Command(OnTapRefresh);
ImageScale = 1;
}
async void OnTapRefresh(Object obj)
{
ImageScale = 0.8;
await ExecuteRefreshCommand();
await Task.Delay(100);
ImageScale = 1;
}
The tap gesture works to refresh the list and I don't get any errors, but the image doesn't scale

You should use RaisePropertyChanged (or somethink similar - generally PropertyChanged from INotifyPropertyChanged) of BaseViewModel in setter of ImageScale.

Related

Xamarin Forms MVVM Databinding failing when I'm binding to a single object

I'm having an issue with data not binding correctly on a details page when I have clicked through from a ListView via a button. The ListView binds perfectly and the object gets passed through to the details page. The Id of the object is read and a full version of the object is called from an API and set to a new instance of the object. When I add a breakpoint, the full object is available, but Labels on the view aren't populated. Here is the ViewModel:
DetailsViewModel.cs
public class DetailsViewModel
{
public Deal Deal { get; set; }
public int DealId { get; set; }
public DetailsViewModel(int id)
{
Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if(deal != null)
{
Deal = deal;
}
}
}
The codebehind looks like this:
DetailPage.Xaml.cs
DetailsViewModel viewModel;
int dealId;
public DetailPage(int id)
{
InitializeComponent();
dealId = id;
viewModel = new DetailsViewModel(dealId);
BindingContext = viewModel;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.GetDeal();
}
And the Xaml file is
DetailPage.Xaml
<ContentPage.Content>
<ScrollView>
<StackLayout x:Name="detailsLayout">
<Label Text="{Binding Deal.Name}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
When I put a breakpoint in Deal = deal on DetailsViewModel, the Deal object exists and has the correct data, but I just get a blank screen. I have tried Labels with Text="{Binding Name}" and Text="{Binding Deal.Name}".
I have also tried manually creating a deal in the GetDeal function of the ViewModel and still nothing is bound.
1) Ensure your property Notifies the UI of a change implementing the INotifyPropertyChanged interface. See https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
2) Ensure the set is done on the UI thread using Device.BeginInvokeOnMainThread. https://learn.microsoft.com/fr-fr/dotnet/api/xamarin.forms.device.begininvokeonmainthread?view=xamarin-forms
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace YourNamespace
{
public class DetailsViewModel : INotifyPropertyChanged
{
private Deal _deal;
public Deal Deal
{
get => _deal;
set
{
if (_deal != value)
{
_deal = value;
OnPropertyChanged();
}
}
}
public int DealId { get; set; }
public DetailsViewModel(int id)
{
//!! useless assignation
//Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if (deal != null)
{
//Ensure we are on UI thread
Device.BeginInvokeOnMainThread(() => Deal = deal);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Unable to show Xamarin Forms MVVM binding result in listview

I am trying to implement MVVM approach in my xamarin forms application. During the implementations, I have hit a road block. I am unable to populate the list view with the data that i recieve from the server. I am unable to identify the binding issue.
Please let me know where is my mistake? What am I missing?
View Code
<?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="Test.Views.SubtaskPage"
Title="Select Subtask"
xmlns:viewModels="clr-namespace:Test.ViewModels; assembly=Test">
<ContentPage.BindingContext>
<viewModels:SubtaskPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="tbiAddSubtask" Text="Add Subtask" Clicked="tbiAddSubtask_Clicked"/>
</ContentPage.ToolbarItems>
<StackLayout Orientation="Vertical" Padding="10">
<ListView x:Name="lstSubtasks" ItemSelected="lstSubtasks_ItemSelected" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsBusy}" ItemsSource="{Binding SubtaskList}}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem x:Name="menuAddTimeSpent" Clicked="menuItem_Clicked" CommandParameter="{Binding Ticket}" Text="Menu" />
</ViewCell.ContextActions>
<StackLayout Padding="20,0,0,0" HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<Label Text="{Binding Subject}" VerticalTextAlignment="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Response Class Code
public class SubtasksResponse
{
public int Status { get; set; }
public string Message { get; set; }
public List<Ticket> Subtasks { get; set; }
}
View Model Code
public class SubtaskPageViewModel : INotifyPropertyChanged
{
private SubtasksResponse _subtaskList;
public SubtasksResponse SubtaskList
{
get { return _subtaskList; }
set
{
_subtaskList = value;
OnPropertyChanged(nameof(SubtaskList));
}
}
private Command _refreshCommand;
public Command RefreshCommand
{
get
{
return _refreshCommand;
}
}
bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
OnPropertyChanged(nameof(IsBusy));
}
}
public SubtaskPageViewModel()
{
_refreshCommand = new Command(RefreshList);
}
async void RefreshList()
{
SubtaskList = await PopulateSubtaskList();
}
async Task<SubtasksResponse> PopulateSubtaskList()
{
RestService rs = new RestService();
IsBusy = true;
IsBusy = false;
var subtaskList = new SubtasksResponse();
subtaskList = await rs.GetSubtasksAsync(Convert.ToInt32(Application.Current.Properties["UserId"]));
return subtaskList;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
For starters we see you are binding the ListView to ItemsSource="{Binding SubtaskList} - when we then look at the ViewModel it seems that SubtaskList is of type SubtasksResponse, that type only has 3 properties.
But the item template inside your ListView is not using any of those 3 properties... it's using Ticket and Subject.
Are this properties of the class Subtasks? If so you need to bind the ListView directly to the List property for it to pick up the items in that collection.

Xamarin View not Binding from viewModel after Constructor

I have a simple View that displays a label with a Question that is being bound from my ViewModel. now if I set the property in my constructor I see the Label displaying whatever I set it to. if I populated from my command function I do not see the label changed. The funny thing is that if I set the Title property (a simple string that has a get and set), then that changes no matter where I set it. but for some reason this particular property does not want to show the changes to it. I have tried simplifying this as much as I can. I tried to define a public string property in my ViewModel and again if I set it in the Constructor than it binds other wise if it is being set in my Command Function then it does not change.
here is my XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Pre.MyPage"
Title="{Binding Title}"
Icon="about.png">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="{Binding MyClassObj.Question, Mode=TwoWay}"/>
</StackLayout>
</ContentPage>
Here is my Code behind
public partial class MyPage : ContentPage
{
MyViewModel vm;
MyViewModel ViewModel => vm ?? (vm = BindingContext as MyViewModel);
public MyPage()
{
InitializeComponent();
BindingContext = new MyViewModel(Navigation);
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadQuestionCommand.Execute("1");
}
}
Here is my ViewModel
public class MyViewModel : ViewModelBase
{
public MyClass MyClassObj {get;set;}
ICommand loadQuestionCommand;
public ICommand LoadQuestionCommand =>
loadQuestionCommand ?? (loadQuestionCommand = new Command<string>(async (f) => await LoadQuestion(f)));
public MyViewModel(INavigation navigation) : base(navigation)
{
Title = "My Title";
}
async Task<bool> LoadQuestion(string id)
{
if (IsBusy)
return false;
try
{
IsBusy = true;
MyClassObj = await StoreManager.QuestionStore.GetQuestionById(id);
//MyClassObject is populated when I break here
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
return true;
}
I don't see where you are firing the INofityPropertyChanged event for your MyClassObj property.
Instead of just:
public MyClass MyClassObj {get;set;}
you should have something like:
MyClass myClassObj;
public MyClass MyClassObj
{
get {return myClassObj;}
set
{
//if they are the same you should not fire the event.
//but since it's a custom object you will need to override the Equals
// of course you could remove this validation.
if(myClassObj.Equals(value))
return;
myClassObj = value;
//This method or something has to be in your VieModelBase, similar.
NotifyPropertyChanged(nameof(MyClassObj));
}
}
Where the last method
NotifyPropertyChanged(nameof(MyClassObj));
is who notifies the View about the changes.

UWP Binding to AutoSuggestBox in MVVM

i am invoking the QuerySubmitted command of the AutoSuggestBox control in UWP.
the command binds to ICommand in the view model.
the problem is it requires to accept AutoSuggestBoxQuerySubmittedEventArgs which is pure UI and it's not acceptable in MVVM.
my code looks like that:
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find"
>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="QuerySubmitted">
<core:InvokeCommandAction Command="{x:Bind ViewModel.SearchCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AutoSuggestBox>
and my view model looks like that:
public DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs> SearchCommand { get; }
public MainPageViewModel()
{
SearchCommand = new DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs>(ExecuteMethod);
}
private void ExecuteMethod(AutoSuggestBoxQuerySubmittedEventArgs o)
{
// CODE HERE
}
ofcours AutoSuggestBoxQuerySubmittedEventArgs is not acceptable in the view model.
looking for alternatives...
same goes to SuggestionChosen...
InvokeCommandAction has a parameter named InputConverter which you can use to convert the event args to some other parameter that can be passed to your ViewModel.
First create a IValueConverter class to extract what you need from your event args like this:-
public class AutoSuggestQueryParameterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// cast value to whatever EventArgs class you are expecting here
var args = (AutoSuggestBoxQuerySubmittedEventArgs)value;
// return what you need from the args
return (string)args.ChosenSuggestion;
}
}
Then use that converter in your XAML like this:
<Page.Resources>
<converters:AutoSuggestQueryParameterConverter x:Key="ArgsConverter" />
</Page.Resources>
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="QuerySubmitted">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.SearchCommand}"
InputConverter="{StaticResource ArgsConverter}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AutoSuggestBox>
Finally in your viewmodel change your command to accept a string as parameter. So you would have the following in your vm:
public DelegateCommand<string> SearchCommand { get; }
public MainPageViewModel()
{
SearchCommand = new DelegateCommand<string>(ExecuteMethod);
}
private void ExecuteMethod(string o)
{
// CODE HERE
}
You can bind the search string (Text property) to a view model property and the events to parameter-less methods. This way the view model wont have to deal with UI objects:
XAML:
<AutoSuggestBox Header="What's your name?"
TextChanged="{x:Bind ViewModel.FilterUsuals}"
QuerySubmitted="{x:Bind ViewModel.ProcessQuery}"
SuggestionChosen="{x:Bind ViewModel.ProcessChoice}"
ItemsSource="{x:Bind ViewModel.Usuals, Mode=OneWay}"
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay}"
QueryIcon="Find" />
Code behind:
public class MainPageViewModel : SomeViewModelBaseClass
{
/* Boilerplate code and constructor not included */
private string _SearchText;
public string SearchText {/* getter and setter INotyfyPropertyChange compliant */ }
private List<string> _Usuals; // Initialized on constructor
public string Usuals {/* getter and setter INotyfyPropertyChange compliant */ }
public void FilterUsuals()
{
// the search string is in SearchText Example:
Usuals = _UsualsStore.Where(u => u.Contains(_SearchText.ToLower())).ToList();
}
public void ProcessQuery() { /* TODO - search string is in SearchText */ }
public void ProcessChoice() { /* TODO - search string is in SearchText */ }
}
If you don't mind doing non pure MVVM way.
MainPage.xaml :
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find" QuerySubmitted="{x:Bind ViewModel.SearchQuerySubmitted}" IsEnabled="{x:Bind ViewModel.CanExecuteSearchCommand, Mode=TwoWay}"
>
</AutoSuggestBox>
MainPageViewModel.cs :
public class MainPageViewModel : INotifyPropertyChanged
{
private bool _canExecuteSearchCommand;
public MainPageViewModel()
{
this.CanExecuteSearchCommand = true;
}
public bool CanExecuteSearchCommand
{
get { return _canExecuteSearchCommand; }
set
{
bool changed = _canExecuteSearchCommand != value;
_canExecuteSearchCommand = value;
if(changed)
this.OnPropertyChanged();
}
}
public void SearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
// Just example disabling SearchBox
this.CanExecuteSearchCommand = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.cs :
MainPageViewModel ViewModel = new MainPageViewModel();
UWP Binding Command/Delegate to AutoSuggestBox in MVVM
For UWP Mobile Application
Make a DelegateCommand class
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> executeAction;
Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> executeAction)
: this(executeAction, null)
{
//var a = ((Page)(((Func<object, bool>)(executeAction.Target)).Target)).Name;
//((ViewModel.VMBranchSelection)(executeAction.Target)).;
}
public DelegateCommand(Action<T> executeAction, Func<object, bool> canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void Execute(object parameter)
{
executeAction((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
In View Model
public ICommand SuggessionSelectCity_QuerySubmitted
{
get { return new DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs>(this.SuggessionSelectCityQuerySubmitted); }
}
private void SuggessionSelectCityQuerySubmitted(AutoSuggestBoxQuerySubmittedEventArgs obj)
{
if (obj.ChosenSuggestion != null)
{
AutosuggestionTextBoxName.Text = ((ModelName) (obj.ChosenSuggestion)).Model's Property name;
//or
AutosuggestionTextBoxName.Text =(obj.ChosenSuggestion).property name
}
else
{
}
}
In XAML Code
<AutoSuggestBox Grid.Column="1" x:Name="SuggessionSelectCity"
PlaceholderText="Search by keywords" QueryIcon="Find"
ItemsSource="{Binding PApplicantCityList}"
HorizontalAlignment="Center" VerticalAlignment="Center" DisplayMemberPath="Description" Width="250" Height="45">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="TextChanged">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCityTextChange}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="QuerySubmitted">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCity_QuerySubmitted}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="SuggestionChosen">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCitySuggestionChosen}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</AutoSuggestBox>
</Grid>
Create a list in View Model for Autosuggestion TextBox Itemssource
private ObservableCollection<ResultMasterModel> ApplicantCityList;
public ObservableCollection<ResultMasterModel> PApplicantCityList
{
get { return ApplicantCityList; }
set { this.SetProperty(ref this.ApplicantCityList, value); }
}
add some hard code value in above list
Create a Model In Model Folder
public class ResultMasterModel
{
public string Code { get; set; }
public string Description { get; set; }
}

Virtualizing stackpanel - virtualized item visibility

I have a scenario where I am using a list box to display a large list of ViewModels, each with a visibility property that changes depending on application logic.
The problem I am experiencing is that when the visibility of a 'virtualized' item changes, the scrollbar isn't updated to reflect the scrollable range until the items are brought into view by manually scrolling.
This is clearly caused by the fact that the virtualized items are not having the visibility binding evaluated and so do not add to the the scrollable range, but how can I get around the issue without disabling visualization?
Note : I'm aware that I could use a filtering CollectionView, but having the Visibility property works better with my application logic.
Below is some code that demonstrates the problem.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; private set; }
public Visibility Visibility
{
get { return m_visibility; }
set
{
m_visibility = value;
RaisePropertyChanged("Visibility");
}
}
public ViewModel(string name)
{
Name = name;
}
protected void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Visibility m_visibility = Visibility.Visible;
}
public partial class MainWindow : Window
{
public List<ViewModel> ViewModels { get; private set; }
public MainWindow()
{
ViewModels = new List<ViewModel>();
for(int i = 0; i < 100; ++i)
{
ViewModels.Add(new ViewModel("item_" + i));
}
DataContext = this;
InitializeComponent();
}
void OnHideItemsClick(object sender, EventArgs e)
{
for (int i = 30; i < ViewModels.Count; ++i)
{
ViewModels[i].Visibility = Visibility.Collapsed;
}
}
void OnShowItemsClick(object sender, EventArgs e)
{
for (int i = 30; i < ViewModels.Count; ++i)
{
ViewModels[i].Visibility = Visibility.Visible;
}
}
}
<DockPanel>
<UniformGrid Columns="2" DockPanel.Dock="Top">
<Button Content="Hide offscreen items" Click="OnHideItemsClick" />
<Button Content="Show offscreen items" Click="OnShowItemsClick" />
</UniformGrid>
<ListBox ItemsSource="{Binding ViewModels}" HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Visibility" Value="{Binding Visibility}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="1" BorderThickness="1" BorderBrush="Green">
<TextBlock Text="{Binding Name}" Margin="5" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>