Xamarin form activityindicator not working in constructor - xamarin

I am working on Xamarin forms,I need to display data from database on form load.
So I want to display Activity indicator when database operation is taking time.
I am setting ActivityIndicator to true on constructor load andat the end setting it to false.
But its not showing
Here is code
Xaml is as below
<ContentPage.Content>
<StackLayout VerticalOptions="StartAndExpand" Padding="5,5,5,5">
<ListView HasUnevenRows="True" RowHeight="100" HeightRequest="-1" x:Name="ListViewAppointments" VerticalOptions="StartAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="20,0,20,0" ColumnSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!--<BoxView Color="#f7f7f7" Grid.Column="0" Grid.RowSpan="2"/>
<BoxView Color="#ffffff" Grid.Column="1" Grid.RowSpan="2"/>-->
<Image Grid.RowSpan="2" Grid.Column="0" Source="documenticon.png" Aspect="AspectFit"></Image>
<Label TextColor="#00344e" FontAttributes="Bold" Text="{Binding ReportName}" Grid.Row="0" Grid.Column="1" VerticalTextAlignment="End"></Label>
<Label TextColor="#0073ae" Text="{Binding PrintDate}" Grid.Row="1" Grid.Column="1" VerticalTextAlignment="Start"></Label>
<Image Grid.RowSpan="2" Grid.Column="2" Source="downloadicon.png" Aspect="AspectFit"></Image>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ActivityIndicator x:Name="ActLoder" HorizontalOptions="CenterAndExpand" Color="#ffffff" VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
and code behind
public partial class CurrentDocuments : ContentPage
{
public CurrentDocuments()
{
InitializeComponent();
new Task(Initializer).Start();
}
public async void Initializer()
{
ShowLoader(true);
NavigationPage.SetBackButtonTitle(this, "");
Title = "CURRENT DOCUMENTS";
DocumentsResponse appointsData = null;
await Task.Run(async () =>
{
appointsData = await GetCurrentDocuments();
}).ContinueWith(_ =>
{
Device.BeginInvokeOnMainThread(() => {
ListViewAppointments.ItemsSource = appointsData.ListOfDocuments;
ShowLoader(false);
});
});
}
private async Task<DocumentsResponse> GetCurrentDocuments()
{
DocumentManager manager = new DocumentManager();
var result = await manager.GetCurrentDocuments(Application.Current.Properties["SessionId"].ToString());
return result;
}
public async void ShowLoader(bool isVisible)
{
ActLoder.IsRunning = isVisible;
ActLoder.IsVisible = isVisible;
ActLoder.IsEnabled = true;
}
}

GetCurrentDocuments is returning a Task<DocumentsResponse> and you are not awaiting it to activate that Task.
var appointsData = await GetCurrentDocuments();
But, you should not await in an .ctor.
Something like this will get you started (I did this on the fly, so correct any typos/syntax errors/etc:
public partial class CurrentDocuments : ContentPage
{
public CurrentDocuments()
{
InitializeComponent();
new Task(Initializer).Start();
}
public async void Initializer()
{
ShowLoader(true);
NavigationPage.SetBackButtonTitle(this, "");
Title = "CURRENT DOCUMENTS";
Task<DocumentsResponse> appointsData;
await Task.Run(async () =>
{
appointsData = await GetCurrentDocuments();
}).ContinueWith(_ =>
{
Device.BeginInvokeOnMainThread(() => {
ListViewAppointments.ItemsSource = appointsData;
ShowLoader(false);
});
});
}
public void ShowLoader(bool isVisible)
{
ActLoder.IsRunning = isVisible;
ActLoder.IsVisible = isVisible;
ActLoder.IsEnabled = true;
}
public async Task<DocumentsResponse> GetCurrentDocuments()
{
DocumentManager manager = new DocumentManager();
var result = await manager.GetCurrentDocuments(Application.Current.Properties["SessionId"].ToString());
return result;
}
}

Related

How to implement a command to delete an item from my database in xamarinforms mvvm style

Currently i have a clicked function that delete an items :
async void DeleteButtonClicked(object sender, EventArgs e)
{
ImageButton button = sender as ImageButton;
var agenda = button.BindingContext as Agenda;
await App.Database.DeleteAgendaAsync(agenda);
await Navigation.PopAsync();
}
I want to implement this MVVM style with a command, the basic of my app is that i have an AgendaPage that loads items in a collection view, right now i want to be able to call the command on this icon and so it delete the item.
Thanks for your help.
Currently in my PageViewModel i only have this
I have to pass the current agenda to the DeleteAgendaAsync() method but it's unclear to me where to get it.
public Command DeleteAgendaCommand { get; set; }
public AgendaPageViewModel()
{
DeleteAgendaCommand = new Command(async () => await DeleteAgenda());
}
async Task DeleteAgenda()
{
await App.Database.DeleteAgendaAsync();
}
AgendaDatabase.cs in the Database folder
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
using Calculette.Models;
using System.Threading.Tasks;
using Calculette.ViewModel;
namespace Calculette.Database
{
public class AgendaDatabase
{
readonly SQLiteAsyncConnection database;
public AgendaDatabase(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<Agenda>().Wait();
}
// Get all agenda
public Task<List<Agenda>> GetAgendasAsync()
{
return database.Table<Agenda>().ToListAsync();
}
// Get specific agenda
public Task<Agenda> GetAgendaAsync(int id)
{
return database.Table<Agenda>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}
// Insert new agenda (save)
public Task<int> SaveAgendaAsync(Agenda agenda)
{
if (agenda.ID != 0)
{
return database.UpdateAsync(agenda);
}
else
{
return database.InsertAsync(agenda);
}
}
//Delete specific agenda
public Task<int> DeleteAgendaAsync(Agenda agenda)
{
return database.DeleteAsync(agenda);
}
public Task<int> AddAgendaAsync(Agenda agenda)
{
return database.InsertAsync(agenda);
}
}
}
Agenda.cs in the Models folder
[Table("Agenda")]
public class Agenda
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Topic { get; set; }
public string Duration { get; set; }
public DateTime Date { get; set; }
This is the NewFormViewModel.cs which is used to create new agenda items to the collectionview, it feels like i would need to access all the items added there, but im unsure on how to do that for the DeleteCommand
using Calculette.Database;
using Calculette.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace Calculette.ViewModel
{
class NewFormViewModel : BaseViewModel
{
public Command AgendaSaveFormCommand { get; set; }
public NewFormViewModel()
{
// Initialise la valeur du DatePicker a celle d'aujourd'hui
this.Date = DateTime.Now;
// Commande pour la sauvegarde sur la page NewFormPage ( voir SaveForm() plus bas)
AgendaSaveFormCommand = new Command(async () => await SaveForm(), () => !IsBusy);
}
// Création des propriétés d'un agenda
private string topic;
public string Topic
{
get => topic;
set
{
topic = value;
NotifyPropertyChanged();
}
}
private string duration;
public string Duration
{
get => duration;
set
{
duration = value;
NotifyPropertyChanged();
}
}
private DateTime date;
public DateTime Date
{
get => date;
set
{
date = value;
NotifyPropertyChanged();
}
}
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
NotifyPropertyChanged();
AgendaSaveFormCommand.ChangeCanExecute();
}
}
public int ID { get; }
// Methode qui enregistre un agenda et l'ajoute a la collection d'agenda de AgendaPage
async Task SaveForm()
{
IsBusy = true;
await Task.Delay(4000);
IsBusy = false;
// Agenda agenda = new Agenda();
//ObservableCollection<Agenda> agenda = new ObservableCollection<Agenda>();
Agenda agenda = new Agenda();
agenda.Topic = Topic;
agenda.Date = Date;
agenda.Duration = Duration;
await App.Database.SaveAgendaAsync(agenda);
await Application.Current.MainPage.DisplayAlert("Save", "La tâche a été enregistrée", "OK");
await Application.Current.MainPage.Navigation.PopAsync();
}
}
AgendaPage.xaml
<CollectionView Grid.Row="2" Margin="25" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" x:Name="AgendaCollection" ItemsSource="{Binding Agendas}"> <!--ItemsSource="{Binding AngedaCollection}" -->
<CollectionView.Header>
<StackLayout Orientation="Horizontal" Spacing="220">
<Label Text="Agenda" TextColor="Black" FontSize="18"/>
<StackLayout Orientation="Horizontal">
<ImageButton Source="iconplus.png" HeightRequest="30" WidthRequest="30" Clicked="GoToNewFormPage"></ImageButton>
<ImageButton Source="iconmoins.png" HeightRequest="30" WidthRequest="30" Clicked="DeleteButtonClicked"></ImageButton>
</StackLayout>
</StackLayout>
</CollectionView.Header>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="20"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate >
<DataTemplate>
<pv:PancakeView HasShadow="True" BackgroundColor="White" VerticalOptions="StartAndExpand "
HorizontalOptions="FillAndExpand">
<Grid VerticalOptions="StartAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="{Binding Color}" WidthRequest="3" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<Expander Grid.Column="1" >
<Expander.Header>
<Grid HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3.5*"/>
</Grid.ColumnDefinitions>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Date, StringFormat='{0:dd}'}" TextColor="#008A00" FontSize="27"
HorizontalOptions="Center"/>
<Label Text="{Binding Date, StringFormat='{0:MMMM}'}" TextColor="Black" FontSize="10"
HorizontalOptions="Center" Margin="0,-10,0,0" FontAttributes="Bold"/>
<ImageButton Source="iconplus.png" HorizontalOptions="Center" HeightRequest="30" WidthRequest="30" Clicked="GoToFormPage"></ImageButton>
</StackLayout>
<BoxView Grid.Column="1" BackgroundColor="#F2F4F8" WidthRequest="1" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<StackLayout x:Name="topicLayout" Grid.Column="2" HorizontalOptions="Start" VerticalOptions="Center" Margin="20">
<Label Text="{Binding Topic}" TextColor="#008A00" FontSize="15" FontAttributes="Bold"/>
<Label Text="{Binding Duration}" TextColor="#2F3246" FontSize="12" Margin="0,-10,0,0"/>
<ImageButton Source="iconmoins.png" HeightRequest="30" WidthRequest="30" Command="{Binding Source={x:Reference AgendaCollection}, Path=AgendaPageViewModel.DeleteAgendaCommand}"
CommandParameter="{Binding .}"></ImageButton>
</StackLayout>
</Grid>
</Expander.Header>
<Grid HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3.5*"/>
</Grid.ColumnDefinitions>
<BoxView Grid.Column="1" BackgroundColor="#F2F4F8" WidthRequest="1" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<StackLayout Grid.Column="2" Spacing="10">
<Label Text="Tâches" TextColor="Black" FontSize="15" Margin="20,0"/>
<StackLayout BindableLayout.ItemsSource="{Binding Speakers}" HorizontalOptions="Start" VerticalOptions="Center" Margin="20,0,0,20">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label TextColor="#2F3246" FontSize="12">
<Label.FormattedText>
<FormattedString>
<FormattedString.Spans>
<Span Text="{Binding Time}"/>
<Span Text=" - "/>
<Span Text="{Binding Name}" FontAttributes="Bold"/>
</FormattedString.Spans>
</FormattedString>
</Label.FormattedText>
</Label>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</Grid>
</Expander>
</Grid>
</pv:PancakeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Im not sure where you have your delete button, so here are two possible ways of doing this:
1) You select an Agenda from the CollectionView and then use one Button to delete this agenda:
Add the SelectedItem as a Property to the ViewModel like that:
ObservableCollection<Agenda> agendas = new ObservableCollection<Agenda>();
public ObservableCollection<Agenda> Agendas { get => agendas; set => agendas = value; }
public Agenda SelectedItem { get; set; }
public Command Delete { get; set; }
public ViewModel()
{
Agendas.Add(new Agenda() { Name = "test" });
Agendas.Add(new Agenda() { Name = "test2" });
Agendas.Add(new Agenda() { Name = "test3" });
Delete = new Command(new Action<object>((obj) =>
{
DeleteAsync(SelectedItem);
}));
}
private void DeleteAsync(Agenda selectedItem)
{
//delete Agenda here
}
To use this simply bind the SelectedItem property of the CollectionView to your ViewModel:
<StackLayout>
<StackLayout.BindingContext>
<local:ViewModel/>
</StackLayout.BindingContext>
<CollectionView ItemsSource="{Binding Agendas}"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame>
<StackLayout>
<Label Text="{Binding Name}" FontSize="120" Margin="12"/>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="Delete" Command="{Binding Delete}" />
</StackLayout>
2) You have a button to delete an Agenda for each Agenda:
You can setup your ViewModel similar to this:
ObservableCollection<Agenda> agendas = new ObservableCollection<Agenda>();
public ObservableCollection<Agenda> Agendas { get => agendas; set => agendas = value; }
public Command Delete { get; set; }
public ViewModel()
{
Delete = new Command(new Action<object>((obj) =>
{
DeleteAsync((Agenda) obj);
}));
}
private void DeleteAsync(Agenda selectedItem)
{
//delete Agenda here
}
Then you can set this up in xaml like this:
<StackLayout>
<StackLayout.BindingContext>
<local:ViewModel/>
</StackLayout.BindingContext>
<CollectionView x:Name="AgendaCollection"
ItemsSource="{Binding Agendas}" >
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame>
<StackLayout>
<Label Text="{Binding Topic}" FontSize="120" Margin="12"/>
<Button Text="Delete"
Command="{Binding Source={x:Reference AgendaCollection}, Path=BindingContext.Delete}"
CommandParameter="{Binding .}"/>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Note you can directly Bind to a Command, you dont have to use ButtonClicked and than pass it on.
In this case you have to change the Binding Source to the CollectionView (give that a x:Name and Bind to that) because the Binding Context inside the DataTemplate will be set to the Children of the ItemSource (the individual Agendas). That way the command is automatically executed without the Clicked event.
To pass on the Agenda on which delete was clicked to the Command you can set the CommandParameter. Here it Binds to itself (As i said CollectionView will set the DataContext of its elements to the corresponding individual element.)

Show JSON response through binding (Xamarin forms)

i showed json response now i want to it to show like in a proper format in the editor section. Below is my code: (i am trying this from past two days but i dont know how to show this in a proper format)
Page 1 from where i passed the json data:
public partial class MainPage : ContentPage
{
private static readonly HttpClient client = new HttpClient();
public String test;
public MainPage()
{
InitializeComponent();
// Task.Run(async () => await GetinfoAsync());
GetinfoAsync();
}
public async Task GetinfoAsync()
{
var responseString = await client.GetStringAsync("https://reqres.in/api/users/2");
UserResponse result = JsonConvert.DeserializeObject<UserResponse>(responseString);
if (result != null)
{
Device.BeginInvokeOnMainThread(() =>
{
test = dis.Text = "Id:- " + result.Data.id + "\nEmail:- " + result.Data.email + "\nFirst Name:- " + result.Data.first_name + "\nLast Name:- " + result.Data.last_name + "\nImage:- " + result.Data.avatar; dis.Text = test;
});
}
}
private async void click_me(Object sender, EventArgs args)
{
await this.Navigation.PushAsync(new Data(test));
}
Page 2 where i get the json data from page 1
public partial class Data : ContentPage
{
public MyUser obj;
public String show;
public Data(String test)
{
show = test;
InitializeComponent();
displaylabel.Text = test;
}
}
Page 2 Xaml
<StackLayout Padding="20">
<Editor Text="{Binding obj.id}" IsReadOnly="True"/>
<Editor Text="{Binding obj.first_name}" IsReadOnly="True"/>
<Editor Text="{Binding obj.last_name}" IsReadOnly="True"/>
<Editor Text="{Binding obj.email}" IsReadOnly="True"/>
<Editor Text="Image" IsReadOnly="True"/>
<Image HeightRequest="100" WidthRequest="100" Source="{Binding obj.avatar}"/>
<Label Text="show json"
x:Name="displaylabel"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
myuser.cs
public class MyUser
{
public int id { get; set; }
public string email { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string avatar { get; set; }
}
public class UserResponse
{
public MyUser Data { get; set; }
}
Create new page send json data from your last page and populate here by belo code.
XAML code
<Grid Margin="20,50">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Editor Grid.Row="0" Grid.Column="0" Text="id" IsReadOnly="True"/>
<Editor x:Name="txtId" Grid.Row="0" Grid.Column="1" IsReadOnly="True"/>
<Editor Grid.Row="1" Grid.Column="0" Text="First name" IsReadOnly="True"/>
<Editor x:Name="txtFirstname" Grid.Row="1" Grid.Column="1" IsReadOnly="True"/>
<Editor Grid.Row="2" Grid.Column="0" Text="Last name" IsReadOnly="True"/>
<Editor x:Name="txtLastname" Grid.Row="2" Grid.Column="1" IsReadOnly="True"/>
<Editor Grid.Row="3" Grid.Column="0" Text="Email" IsReadOnly="True"/>
<Editor x:Name="txtEmail" Grid.Row="3" Grid.Column="1" IsReadOnly="True"/>
<Editor Grid.Row="4" Grid.Column="0" Text="Image" IsReadOnly="True"/>
<Image x:Name="imgUser" Grid.Row="4" Grid.Column="1" HeightRequest="100" WidthRequest="100" />
</Grid>
Code behind
public partial class Homepage : ContentPage
{
private static readonly HttpClient client = new HttpClient();
myuser _Data;
public Homepage(myuser Data)
{
InitializeComponent();
//this.BindingContext = new MainPageViewModel();
_Data = Data;
}
protected override void OnAppearing()
{
base.OnAppearing();
txtId.Text = _Data.id.ToString();
txtFirstname.Text = _Data.first_name;
txtLastname.Text = _Data.last_name;
txtEmail.Text = _Data.email;
imgUser.Source = _Data.avatar;
}
}
Hope it will help you
Thanks
First, you can follow the MVVM pattern to make this easier in the long run.
But if you want to proceed with your existing code then you can try this:
First, setup your binding context in your Page 2 xaml:
<ContentPage x:Name="MyPage" ... />
<StackLayout Padding="20">
<Editor BindingContext="{x:Reference Name=MyPage}" Text="{Binding obj.id}" IsReadOnly="True"/>
<Editor BindingContext="{x:Reference Name=MyPage}" Text="{Binding obj.first_name}" IsReadOnly="True"/>
<Editor BindingContext="{x:Reference Name=MyPage}" Text="{Binding obj.last_name}" IsReadOnly="True"/>
<Editor BindingContext="{x:Reference Name=MyPage}" Text="{Binding obj.email}" IsReadOnly="True"/>
<Editor BindingContext="{x:Reference Name=MyPage}" Text="Image" IsReadOnly="True"/>
<Image x:Name="myImage" HeightRequest="100" WidthRequest="100"/>
<Label Text="show json"
x:Name="displaylabel"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
In your code behind:
Instead of passing string test, you can declare a UserReponse object:
private static readonly HttpClient client = new HttpClient();
public String test; //instead of this
public UserReponse result; //do this
Then modify this:
public async Task GetinfoAsync()
{
var responseString = await client.GetStringAsync("https://reqres.in/api/users/2");
result = JsonConvert.DeserializeObject<UserResponse>(responseString); //modify to this
if (result != null)
{
Device.BeginInvokeOnMainThread(() =>
{
test = dis.Text = "Id:- " + result.Data.id + "\nEmail:- " + result.Data.email + "\nFirst Name:- " + result.Data.first_name + "\nLast Name:- " + result.Data.last_name + "\nImage:- " + result.Data.avatar; dis.Text = test;
});
}
}
Then pass it:
await this.Navigation.PushAsync(new Data(result));
Replace your page 2 code behind with:
public partial class Data : ContentPage
{
private MyUser _obj;
public MyUser obj
{
get{return _obj;}
set
{
_obj = value;
OnPropertyChanged();
}
}
public String show;
public Data(UserReponse result)
{
show = test;
InitializeComponent();
obj = result.Data;
myImage.Source = ImageSource.FromUri(new Uri(obj.avatar));
displaylabel.Text = test;
}
}
Try to pass the UserResponse to the Data Page directly,and binding it to the page:
public partial class MainPage : ContentPage
{
private static readonly HttpClient client = new HttpClient();
public UserResponse test;
public MainPage()
{
InitializeComponent();
// Task.Run(async () => await GetinfoAsync());
GetinfoAsync();
}
public async Task GetinfoAsync()
{
var responseString = await client.GetStringAsync("https://reqres.in/api/users/2");
UserResponse result = JsonConvert.DeserializeObject<UserResponse>(responseString);
if (result != null)
{
Device.BeginInvokeOnMainThread(() =>
{
test = result;
});
}
}
private async void click_me(Object sender, EventArgs args)
{
await this.Navigation.PushAsync(new Data(test));
}
}
the Data page:
public partial class Data : ContentPage
{
public UserResponse obj;
public String show;
public Data(UserResponse test)
{
obj= test;
InitializeComponent();
BindingContext = obj;
}
}
the xaml :
<StackLayout Padding="20">
<Editor Text="{Binding Data.id}" IsReadOnly="True"/>
<Editor Text="{Binding Data.first_name}" IsReadOnly="True"/>
<Editor Text="{Binding Data.last_name}" IsReadOnly="True"/>
<Editor Text="{Binding Data.email}" IsReadOnly="True"/>
<Editor Text="Image" IsReadOnly="True"/>
<Image HeightRequest="100" WidthRequest="100" Source="{Binding Data.avatar}"/>
<Label Text="show json"
x:Name="displaylabel"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>

View IsEnabled Property is not working on Xamarin Forms

Here is my Listview
Inside Listview button IsEnabled Property not working,IsEnabled False not working.
I followed This step but still its not working
https://forums.xamarin.com/discussion/47857/setting-buttons-isenabled-to-false-does-not-disable-button
Inside My ViewModel
OrderItems=PopuldateOrders();// getting List Items
<ListView x:Name="OrderItems" VerticalOptions="Fill"
BackgroundColor="White" HasUnevenRows="True"
SeparatorVisibility="None" ItemsSource="{Binding OrderItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView BackgroundColor="White">
<Grid BackgroundColor="Transparent" Margin="0" VerticalOptions="FillAndExpand" x:Name="Item">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="18*"/>
<ColumnDefinition Width="18*"/>
<ColumnDefinition Width="17*"/>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="17*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding PullSheetId}" Grid.Row="0" IsVisible="False"/>
<controls:CheckBox Checked="{Binding IsChecked}" Grid.Row="0" Grid.Column="0" IsVisible="{Binding IsEnableShipBtn}" Scale=".8"/>
<Label Text="{Binding KitSKU}" Grid.Row="0" Grid.Column="1"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="Black"/>
<Label Text="{Binding SKU}" Grid.Row="0" Grid.Column="2"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="{Binding ItemColor}"/>
<Label Text="{Binding ReqPackQty}" Grid.Row="0" Grid.Column="3"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="Black"/>
<local:EntryStyle Scale=".6" Text="{Binding ScanQuantity}" Grid.Row="0" Keyboard="Numeric"
Grid.Column="4" HorizontalTextAlignment="Center"
VerticalOptions="Center" Placeholder="Qty" IsEnabled="True" x:Name="QtyEntry"
>
<local:EntryStyle.Behaviors>
<eventToCommand:EventToCommandBehavior EventName="TextChanged"
Command="{Binding Source={x:Reference OrderItems}, Path=BindingContext.ChangeItemQty}"
CommandParameter="{Binding Source={x:Reference Item}, Path=BindingContext}"
/>
</local:EntryStyle.Behaviors>
</local:EntryStyle>
<Button Text="Ship" Scale=".6" Grid.Row="0" Grid.Column="5"
VerticalOptions="Center" BackgroundColor="#6eb43a" TextColor="White"
BorderRadius="20" CornerRadius="20" BorderColor="{Binding isError}" BorderWidth="3"
MinimumWidthRequest="60"
x:Name="ShipBtn"
Command="{Binding Source={x:Reference OrderItems}, Path=BindingContext.SubmitSingleItem}"
IsEnabled="{Binding IsEnableShipBtn}" IsVisible="{Binding IsEnableShipBtn}"
CommandParameter="{Binding .}"
/>
</Grid>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<eventToCommand:EventToCommandBehavior EventName="ItemTapped" Command="{Binding PackerItemsItemTapped}"/>
</ListView.Behaviors>
</ListView>
How to solve this?
You could achieve CanExecute method in ICommand to replace the IsEnabled property of Button. You could refer to my demo.
This is a GIF of my demo.
Firstly, You could see MainPage.xaml. Binding the model view and set command for Buttons.
<?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:TestDemo"
x:Class="TestDemo.MainPage">
<!--BindingContext from ButtonExecuteViewModel -->
<StackLayout>
<StackLayout.BindingContext>
<local:ButtonExecuteViewModel/>
</StackLayout.BindingContext>
<Button
Text="click me to enable following button"
Command="{Binding NewCommand}"/>
<Button
Text="Cancel"
Command="{Binding CancelCommand}"/>
</StackLayout>
Here is View Model ButtonExecuteViewModel.cs. You could see the construction method of ButtonExecuteViewModel, it set the execute and canExecute to acheve isEnable of Button.
public class ButtonExecuteViewModel : INotifyPropertyChanged
{
bool isEditing;
public event PropertyChangedEventHandler PropertyChanged;
public ButtonExecuteViewModel()
{
NewCommand = new Command(
execute: () =>
{
IsEditing = true;
RefreshCanExecutes();
},
canExecute: () =>
{
return !IsEditing;
});
CancelCommand = new Command(
execute: () =>
{
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return IsEditing;
});
}
void RefreshCanExecutes()
{
(NewCommand as Command).ChangeCanExecute();
(CancelCommand as Command).ChangeCanExecute();
}
public ICommand NewCommand { private set; get; }
public ICommand CancelCommand { private set; get; }
public bool IsEditing
{
private set { SetProperty(ref isEditing, value); }
get { return isEditing; }
}
//Determine if it can be executed
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));
}
}
I tried all the answers here and nothing worked, I only solved creating a custom button component:
CustomButtonView.xaml
<Button xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomComponents.CustomButton.CustomButtonView"
FontAttributes="Bold"
TextColor="White"
FontSize="20"
TextTransform="Uppercase"
CornerRadius="20"
WidthRequest="200"
HorizontalOptions="Center" />
CustomButtonView.xaml.cs: Here lives the magic of the custom IsEnabled
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomComponents.CustomButton
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomButtonView
{
#region constructors
public CustomButtonView()
{
InitializeComponent();
BackgroundColor = Color.Blue;
Clicked += (_, args) =>
{
if (IsEnabled) Command?.Execute(args);
};
}
#endregion
#region Command
public new static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command),
typeof(ICommand),
typeof(CustomButtonView));
public new ICommand? Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
#endregion
#region IsEnabled
public new static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
nameof(IsEnabled),
typeof(bool),
typeof(CustomButtonView),
true,
propertyChanged: (bindable, _, newValue) =>
{
var component = (CustomButtonView)bindable;
if ((bool)newValue)
{
component.BackgroundColor = Color.Blue;
}
else
{
component.BackgroundColor = Color.Red;
}
});
public new bool IsEnabled
{
get => (bool)GetValue(IsEnabledProperty);
set => SetValue(IsEnabledProperty, value);
}
#endregion
}
}
Now you can use it like any regular button in your XAML:
<customButton:CustomButtonView Text="Button enabled"
Command="{Binding SomeCommand}"
IsEnabled="True"/>
<customButton:CustomButtonView Text="Button disabled"
Command="{Binding SomeCommand}"
IsEnabled="False"/>

Binding list view item template image to ObservableCollection not working

I know that there were many questions about this and I searched a lot and tried everything which I found about this but I can't get it working.
Simply put, for some reason I'm unable to show an image inside a ListView item template.
So I have this ItemViewModel class:
public class ItemViewModel : BaseViewModel, IItemViewModel
{
public ItemViewModel()
{
if (dalInterface == null)
{
dalInterface = ApplicationContext.Container.Resolve<IDalInterface>();
}
if (eventCenter == null)
{
eventCenter = ApplicationContext.Container.Resolve<IEventCenter>();
}
SaveCommand = new Command(SaveChanges, true);
DeleteCommand = new Command(RemoveItem, true);
AddNewItemCommand = new Command(AddNewItem, true);
}
public ICommand SaveCommand { get; set; }
public ICommand DeleteCommand { get; set; }
public ICommand AddNewItemCommand { get; set; }
private Item data;
public int ID { get; private set; }
private string title;
public string Title
{
get { return title; }
set
{
title = value;
NotifyPropertyChanged("Title");
}
}
private string author;
public string Author
{
get { return author; }
set
{
author = value;
NotifyPropertyChanged("Author");
}
}
private string shortDescription;
public string ShortDescription
{
get { return shortDescription; }
set
{
shortDescription = value;
NotifyPropertyChanged("ShortDescription");
}
}
private string buyPrice;
public string BuyPrice
{
get { return buyPrice; }
set
{
buyPrice = value;
NotifyPropertyChanged("BuyPrice");
}
}
private string borrowPrice;
public string BorrowPrice
{
get { return borrowPrice; }
set
{
borrowPrice = value;
NotifyPropertyChanged("BorrowPrice");
}
}
private int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
NotifyPropertyChanged("Quantity");
}
}
private string detailedDescription;
public string DetailedDescription
{
get { return detailedDescription; }
set
{
detailedDescription = value;
NotifyPropertyChanged("DetailedDescription");
}
}
private string imagePath;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
NotifyPropertyChanged("ImagePath");
}
}
private Image image;
public Image Image
{
get { return image; }
set
{
image = value;
NotifyPropertyChanged("Image");
}
}
public void SetData(Item item)
{
data = item;
ID = data.ID;
Author = data.Author;
Title = data.Title;
Quantity = data.Quantity;
ShortDescription = data.ShortDescription;
DetailedDescription = data.DetailedDescription;
BuyPrice = data.BuyPrice;
BorrowPrice = data.BorrowPrice;
Image = GetImage(data.ImagePath);
}
private Image GetImage(string imagePath)
{
var imageUri = new Uri(imagePath, UriKind.RelativeOrAbsolute);
var bitmapImage = new BitmapImage(imageUri);
var image = new Image
{
Source = bitmapImage
};
return Image;
}
private void SaveChanges()
{
UpdateChanges(data);
dalInterface.UpdateItem(data);
}
private void RemoveItem()
{
dalInterface.RemoveItem(data);
}
private void AddNewItem()
{
var newItem = new Item();
if (AllDataCorrect())
{
UpdateChanges(newItem);
dalInterface.AddNewItem(newItem);
eventCenter.Publish(new AddItemEventArgs { OperationSuccess = true });
}
else
{
eventCenter.Publish(new AddItemEventArgs { OperationSuccess = false });
}
}
private void UpdateChanges(Item itemToUpdate)
{
itemToUpdate.Author = Author;
itemToUpdate.Title = Title;
itemToUpdate.BorrowPrice = BorrowPrice;
itemToUpdate.BuyPrice = BuyPrice;
itemToUpdate.DetailedDescription = DetailedDescription;
itemToUpdate.ShortDescription = ShortDescription;
itemToUpdate.Quantity = Quantity;
itemToUpdate.ImagePath = ImagePath;
}
private bool AllDataCorrect()
{
float val = -1.0F;
float.TryParse(BuyPrice, out val);
if (val <= 0.0F)
{
return false;
}
float.TryParse(BorrowPrice, out val);
if (val <= 0.0F)
{
return false;
}
if ((ShortDescription == string.Empty) ||
(DetailedDescription == string.Empty) ||
(Author == string.Empty) ||
(Title == string.Empty)
)
{
return false;
}
if (Quantity <= 0)
{
return false;
}
return true;
}
public void Clear()
{
Author = string.Empty;
Title = string.Empty;
ImagePath = string.Empty;
ShortDescription = string.Empty;
DetailedDescription = string.Empty;
BuyPrice = string.Empty;
BorrowPrice = string.Empty;
Quantity = 0;
}
}
And for this class I have the following user control:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="4"
Style="{StaticResource ImageBorderStyle}">
<Image Source="{Binding Image, Mode=TwoWay}"
MinWidth="80"
MinHeight="80"
Stretch="UniformToFill"/>
</Border>
<Border Grid.Row="0"
Grid.Column="1"
Style="{StaticResource BaseBorderStyle}">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Wiki"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="1"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="Auto"
Text="{Binding ShortDescription}"/>
</Border>
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.Column="1"
Style="{StaticResource BaseBorderStyle}"
HorizontalAlignment="Left"
Width="100">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="About"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="1"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="Auto"
Text="{Binding DetailedDescription}"/>
</Border>
</Grid>
</Grid>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Title"
TextAlignment="Center"
HorizontalAlignment="Stretch"/>
</Border>
<Border Grid.Row="1"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
Text="{Binding Title}"/>
</Border>
<Border Grid.Row="2"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Author"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="3"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
Text="{Binding Author}"/>
</Border>
<Border Grid.Row="4"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Quantity"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="5"
Grid.Column="0"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
Text="{Binding Quantity}"/>
</Border>
<Border Grid.Row="0"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Buy Price"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="1"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
Text="{Binding BuyPrice}"/>
</Border>
<Border Grid.Row="2"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="Borrow Price"
TextAlignment="Center"/>
</Border>
<Border Grid.Row="3"
Grid.Column="1"
Style="{StaticResource DetailsBorderStyle}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBox Style="{StaticResource DataTextBoxStyle}"
HorizontalAlignment="Stretch"
Text="{Binding BorrowPrice}"/>
</Border>
<Button Grid.Row="5"
Grid.Column="1"
Style="{StaticResource SaveButtonStyle}"/>
<Button Grid.Row="5"
Grid.Column="1"
Style="{StaticResource RemoveButtonStyle}"
HorizontalAlignment="Right"/>
</Grid>
</Grid>
Using this I want to show into a page, inside a list view, based on an observable collection, loaded from a database some items.
The page view model is the follow:
public class ManageItemsViewModel : BaseViewModel, IManageItemsViewModel
{
public ManageItemsViewModel()
{
if(dalInterface == null)
{
dalInterface = ApplicationContext.Container.Resolve<IDalInterface>();
}
if(eventCenter == null)
{
eventCenter = ApplicationContext.Container.Resolve<IEventCenter>();
}
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; set; }
public void Refresh()
{
var dalItems = dalInterface.GetAllItems();
foreach(Item item in dalItems)
{
var vm = Items.Where(v => v.ID.Equals(item.ID));
if(vm.Equals(null))
{
var newItemVm = (ItemViewModel)ApplicationContext.Container.Resolve<IItemViewModel>();
newItemVm.SetData(item);
Items.Add(newItemVm);
}
}
NotifyPropertyChanged("Items");
}
public void LoadData()
{
if(Items.Count == 0)
{
var dalItems = dalInterface.GetAllItems();
foreach(Item item in dalItems)
{
var newItemVm = (ItemViewModel)ApplicationContext.Container.Resolve<IItemViewModel>();
newItemVm.SetData(item);
Items.Add(newItemVm);
}
NotifyPropertyChanged("Items");
}
else
{
Refresh();
}
}
}
And the page view is the follow:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Margin="2"
Style="{StaticResource AddButtonStyle}"
Click="GoToAddNewItem"/>
<Button Grid.Row="0"
Margin="2"
HorizontalAlignment="Right"
Style="{StaticResource CloseButtonStyle}"
Click="GoToItems"/>
<ListView Grid.Row="1"
ItemsSource="{Binding Items, Mode=TwoWay}"
Margin="5">
<ListView.ItemTemplate>
<DataTemplate>
<templates:EditableItem/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
That page should be like (at the moment I only have one item in DB for tests):
The problem which I facing now is that I cannot see the Image inside this item template, although all the other properties are correctly shown inside the item template.
I've tried to use the image path as I get it from DB and bind into item xaml the source path property to this, also I tried to have an ImageSource or BitmapImage property inside item VM to have the xaml image source bound to this property but without any success so far.
After reading many questions about and trying again and again without success I'm here...
Any hints about what I'm doing wrong?
Many thanks!
(P.S. The app which I'm working to is an UWP app, maybe it matters...)
You can't use an Image control as the value of the Source property of another Image control.
Use an ImageSource instead:
private ImageSource image;
public ImageSource Image
{
get { return image; }
set
{
image = value;
NotifyPropertyChanged("Image");
}
}
And change the GetImage method to:
private ImageSource GetImage(string imagePath)
{
return new BitmapImage(new Uri(imagePath, UriKind.RelativeOrAbsolute));
}
That said, you could simply drop the Image property and bind directly to ImagePath. Built-in type conversion will automatically convert from a path string to ImageSource.
<Image Source="{Binding ImagePath}"/>
Note also that Mode=TwoWay makes no sense for this Binding.
As I searched again and again about this problem and tried lots of suggested solutions I finally found a solution which is suitable for me and solves this problem.
I implemented a little demo to show how exactly is working. The UWP app is not constraint to interact with the pictures folder only. The images can be loaded from an arbitrary folder on disk.
Hope that this will help others to.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
PickFileCommand = new ActionCommand(PickFile, true);
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand PickFileCommand { get; set; }
private BitmapImage imageSrc;
public BitmapImage ImageSrc
{
get { return imageSrc; }
set
{
imageSrc = value;
NotifyPropertyChanged("ImgSource");
}
}
private async void PickFile()
{
var filePicker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.PicturesLibrary
};
filePicker.FileTypeFilter.Add(".jpg");
filePicker.FileTypeFilter.Add(".jpeg");
filePicker.FileTypeFilter.Add(".png");
StorageFile file = await filePicker.PickSingleFileAsync();
if (file != null)
{
var stream = await file.OpenAsync(FileAccessMode.Read);
var bitmap = new BitmapImage
{
UriSource = new Uri(file.Path, UriKind.Absolute)
};
await bitmap.SetSourceAsync(stream);
ImageSrc = bitmap;
}
}
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
Now, the trick which solved the image problem is in View code behind class.
public sealed partial class MainPage : Page
{
private ViewModel dataContext;
public MainPage()
{
this.InitializeComponent();
dataContext = new ViewModel();
DataContext = dataContext;
}
**private void PageLoaded(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModel dc)
{
dc.PropertyChanged += Dc_PropertyChanged;
}
}
private void Dc_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName.Equals("ImgSource"))
{
if (DataContext is ViewModel dc)
{
ShowImage.Source = dc.ImageSrc;
}
}
}**
}
What was needed it was that the source of the Image UI element to be set explicitly. I made this by subscribing to the PropertyChanged event from ViewModel and set the image source.

Xamarin Forms XAML Label Rotation

I've got a problem with Xamarin.Forms and Label.
I'm trying to set a label on a grid column.
The first image here shows the expected result, which is written in AXML on Android.
The second image here is written in XAML in Xamarin.Forms.
The code in the XAML file is as follows:
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400*"/>
<ColumnDefinition Width="75*"/>
</Grid.ColumnDefinitions>
<WebView Source="{Binding ContentSource}" />
<!--<ProgressBar IsVisible="{Binding IsLoading}"
Progress="{Binding Progress}"/>-->
<Grid Grid.Column="1"
BackgroundColor="#EE7F00"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Label
Text="{Binding DocumentIndex}"
LineBreakMode="NoWrap"
HorizontalOptions="Center"
Rotation="-90"
VerticalOptions="Center" />
</Grid>
</Grid>
How can I expand the height or width of the label to equal to the text length?
Thank you so far
Remove the Grid container for label and place a Box view instead, and set the Grid Row and Column same for both the box view and label. like this
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<WebView Grid.Row="0" Grid.Column="0" Source="{Binding ContentSource}" />
<!--<ProgressBar IsVisible="{Binding IsLoading}"
Progress="{Binding Progress}"/>-->
<BoxView Grid.Row="0" Grid.Column="1" BackgroundColor="#EE7F00" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding DocumentIndex}"
LineBreakMode="NoWrap"
HorizontalOptions="Center"
Rotation="-90"
VerticalOptions="Center" />
</Grid>
I hope this will solve your label length problem after rotation.
I solved this with a custom renderer. In your Forms project:
public class RotatedText : View
{
public static BindableProperty TitleValueProperty = BindableProperty.Create(nameof(TitleValue), typeof(string), typeof(string), null, BindingMode.TwoWay, null,
(bindable, oldValue, newValue) =>
{
});
public string TitleValue
{
get => (string)GetValue(TitleValueProperty);
set => SetValue(TitleValueProperty, value);
}
}
And in your Android project:
[assembly: ExportRenderer(typeof(RotatedText), typeof(RotatedTextRenderer))]
namespace MyNamespace
{
public class RotatedTextRenderer : ViewRenderer
{
private Context _context;
public RotatedTextRenderer(Context c) : base(c)
{
_context = c;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e.NewElement is RotatedText)
{
string title = ((RotatedText)e.NewElement).TitleValue;
SetNativeControl(new RotatedTextView(_context, title));
}
}
}
public class RotatedTextView : Android.Views.View
{
private int DEFAULT_TEXT_SIZE = 30;
private string _text;
private TextPaint _textPaint;
public RotatedTextView(Context c, string title) : base(c)
{
_text = title;
initLabelView();
}
private void initLabelView()
{
this._textPaint = new TextPaint();
this._textPaint.AntiAlias = true;
this._textPaint.TextAlign = Paint.Align.Center;
this._textPaint.TextSize = DEFAULT_TEXT_SIZE;
this._textPaint.Color = new Android.Graphics.Color(0, 0, 0);
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
if (!string.IsNullOrEmpty(this._text))
{
float x = (Width / 2) - DEFAULT_TEXT_SIZE/3;
float y = (Height / 2);
canvas.Rotate(90);
canvas.DrawText(this._text, y, -x, this._textPaint);
}
}
}
}
Then just set TitleValue where ever you use RotatedText. It's a little ugly but I couldn't find a better way.

Resources