How do I use Xamarin.Essentials MediaPicker with MVVM and DataBinding - c#

I am trying to capture a photo in my app and as this needs to be implemented on different pages I need it to work within MVVM architecture. It works perfectly fine when I test it on the Camera page code behind, but as soon as I implement DataBinding and MVVM, the Emulator camera fails to initialize. I don't get any build or deployment errors and have no idea where to start looking. The documentation isn't much help. The images captured needs to be saved and reused each time the app is opened - something to keep in mind perhaps.
Here is my ViewModel:
using System.Collections.Generic;
using System.Text;
using Xamarin.Essentials;
using Xamarin.Forms;
using XamCam.Views;
using MvvmHelpers;
using System.ComponentModel;
using System.Windows.Input;
namespace XamCam.ViewModels
{
public class CameraViewModels : BaseViewModel
{
public CameraViewModels()
{
TakePhoto = new Command(OnTakePhoto);
}
public ICommand TakePhoto { get; }
private Image image; // = new Image();
public Image CamImage
{
get => image;
set
{
if (image == value)
return;
image = value;
OnPropertyChanged();
}
}
async void OnTakePhoto()
{
var result = await MediaPicker.CapturePhotoAsync();
if (result != null)
{
var stream = await result.OpenReadAsync();
image.Source = ImageSource.FromStream(() => stream);
}
}
}
}
This is my View XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:XamCam.ViewModels"
x:DataType="viewmodels:CameraViewModels"
x:Class="XamCam.Views.Camera"
BackgroundColor="AliceBlue">
<ContentPage.BindingContext>
<viewmodels:CameraViewModels/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout BindingContext="CameraViewModel">
<Label Text="Welcome to The XamCam!" />
<Button Text="Take Photo"
Command="{Binding TakePhoto}"/>
<Image BindingContext="{Binding CamImage}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
I suspect that it has something to do with my CamImage property but I am very new to this and I am not sure if this is the correct way to bind a media object.

I suspect that it has something to do with my CamImage property but I am very new to this and I am not sure if this is the correct way to bind a media object.
I suggest you can add ImageSource1 property, not Image property in CameraViewModels.
public class CameraViewModels : INotifyPropertyChanged
{
public CameraViewModels()
{
TakePhoto = new Command(OnTakePhoto);
}
public ICommand TakePhoto { get; }
private ImageSource image;
public ImageSource CamImage
{
get => image;
set
{
if (image == value)
return;
image = value;
RaisePropertyChanged("CamImage");
}
}
async void OnTakePhoto()
{
var result = await Xamarin.Essentials.MediaPicker.CapturePhotoAsync();
if (result != null)
{
var stream = await result.OpenReadAsync();
CamImage = ImageSource.FromStream(() => stream);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<ContentPage
x:Class="App1.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1">
<ContentPage.BindingContext>
<local:CameraViewModels />
</ContentPage.BindingContext>
<StackLayout>
<StackLayout>
<Label Text="Welcome to The XamCam!" />
<Button Command="{Binding TakePhoto}" Text="Take Photo" />
<Image Source="{Binding CamImage}" />
</StackLayout>
</StackLayout>

Related

How to bind content inside another content in Xamarin.UWP?

I'm creating controls in one page that have other controls of their own.
I'm trying to bind the content of a frame inside another bound content, but it crashes if I try to access it the second time.
Also tried to change bind mode to TwoWay with the same result.
Xamarin Forms: 5.0.0.2012
Xamarin.Essentials: 1.6.1
PropertyChanged.Fody: 3.3.2
Main Xaml ->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:test="clr-namespace:Test"
x:Class="Test.MainPage">
<ContentPage.BindingContext>
<test:MainViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Button Text="Some Content View"
Command="{Binding ChangeToContent}"/>
<Button Text="Some other Content View"
Command="{Binding ChangeToOtherContent}"
/>
<Frame Content="{Binding model.MainContent}"/>
</StackLayout>
</ContentPage>
MainViewModel-->
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Xamarin.Forms;
namespace Test
{
public class MainViewModel : INotifyPropertyChanged
{
public MainModel model { get; set; } = new MainModel();
public Command ChangeToContent => new Command(() => {
model.MainContent.Content = new Test1Content();
});
public Command ChangeToOtherContent => new Command(() => {
model.MainContent.Content = new Test2Content();
});
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Main Model -->
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Xamarin.Forms;
namespace Test
{
public class MainModel : INotifyPropertyChanged
{
public Frame SomeContent { get; set; } = new Frame()
{
BackgroundColor = Color.Red,
WidthRequest = 40,
HeightRequest = 40
};
public Frame SomeOtherContent { get; set; } = new Frame()
{
BackgroundColor = Color.Blue,
WidthRequest = 40,
HeightRequest = 40
};
public ContentView MainContent { get; set; } = new ContentView();
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
First Content View -->
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.Test1Content">
<ContentView.Content>
<StackLayout>
<Label Text="This is Test 1 Content" />
<Frame Content="{Binding model.SomeContent}"/>
</StackLayout>
</ContentView.Content>
</ContentView>
Second Content
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.Test2Content">
<ContentView.Content>
<StackLayout>
<Label Text="This is Test 2 Content" />
<Frame Content="{Binding model.SomeOtherContent}"/>
</StackLayout>
</ContentView.Content>
</ContentView>
Result: https://imgur.com/a/caN9gxX
1st Image is the startup
2nd Image is after pressing top button
3rd Image is after pressing the button under
4th Image is of the error's stack trace after pressing top button again
I may have found a solution. The hint was in https://github.com/xamarin/Xamarin.Forms/issues/2713.
If I modify the Command ChangeToOtherContent and ChangeToContent in my Viewmodel like this:
public Command ChangeToContent => new Command(() => {
model.MainContent.Content = null;
model.MainContent.Content = new Test1Content() { BindingContext = this };
});
public Command ChangeToOtherContent => new Command(() => {
model.MainContent.Content = null;
model.MainContent.Content = new Test2Content() { BindingContext = this };
});
the app doesn't crash.
The null content is important if I trigger the command successively. It can probably be replaced by a bool, testing if the user has triggered the command more than once, but that would imply a lot more tests.
I don't understand why the bindingcontext needs to be added, as it is correct the first time it gets rendered. Maybe someone can add to this.

Why does not Xamarin bind property?

I want to save data to application on window closing or application crashes.
When user writes to entry the data gets storen in property, but for some reason the binding does not work.
I followed a course on Udemy for this. I think it has something to do with referencing to different place in PCL.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TheIVInventory.ViewModels
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AddItemPage : ContentPage
{
public AddItemPage()
{
InitializeComponent();
BindingContext = Application.Current;
}
private void Button_Clicked(object sender, EventArgs e) //Item added click.
{
}
}
}
Xaml :
<ContentPage
BackgroundColor="#104850"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="TheIVInventory.ViewModels.AddItemPage">
<StackLayout VerticalOptions="Center" x:Name="formLayout" Margin="20">
<Entry PlaceholderColor="White" Keyboard="Chat" Margin="40" Placeholder="Item Name" TextColor="White" Text="{Binding ItemName}"></Entry>
<Entry PlaceholderColor="White" Keyboard="Numeric" Margin="40" Placeholder="Item Price MIN (€)" TextColor="White"></Entry>
<Entry PlaceholderColor="White" Keyboard="Numeric" Margin="40" Placeholder="Item Price MAX (€)" TextColor="White"></Entry>
<Editor PlaceholderColor="White" Margin="40" VerticalOptions="FillAndExpand" Keyboard="Chat" Placeholder="Item Description" TextColor="White"></Editor>
<Button Text="Save" BackgroundColor="#80EEFF" Margin="10" Clicked="Button_Clicked" ></Button>
<Image Source="konjakki.png" Scale="0.15" AnchorY="0" BackgroundColor="#104850" ></Image>
</StackLayout>
</ContentPage>
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TheIVInventory
{
public partial class App : Application
{
// Setting the item add members.
private const string itemNameKey = "Name";
private const string itemMinPrice = "0";
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage())
{
BarBackgroundColor = Color.FromHex("#104850"),
BarTextColor = Color.White
}; // Making the navigation possible.
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
// Making the add item properties.
public string ItemName
{
get
{
if (Properties.ContainsKey(itemNameKey))
return Properties[itemNameKey].ToString();
return "";
}
set
{
Properties[ItemName] = value;
}
}
public string ItemMinPrice
{
get
{
if (Properties.ContainsKey(itemMinPrice))
return Properties[itemMinPrice].ToString();
return "";
}
set
{
Properties[itemMinPrice] = value;
}
}
}
}
The main issue is that you don't appear to have set a BindingContext for your XAML to refer to.
It also looks to me as if you are trying to implement MVVM structure, but have not entirely understood it.
The points that immediately draw my eye are:
Your view code is within a ViewModel namespace rather than View
(which will prove confusing later on).
You have code that should be within a ViewModel class (which will be your Views BindingContext) in your main App code.
I would suggest that you create a Views namespace and move your AddItemPage code to it;
Create an AddItemViewModel and use it to implement your ItemName and ItemMinPrice properties.
Instead of using a button_clicked event in the code behind, bind the Button Command to an ICommand property type in your ViewModel. Then have your ViewModel instantiate the ICommand to use an internal method to run your save code.
If you want to achieve the MVVM in xamarin forms. Your binding way is wrong, you could create a model, put the ItemMinPrice and ItemName in this model like following code.Achieve the INotifyPropertyChanged interface, when data was changed.
class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string itemMinPrice;
public string ItemMinPrice
{
set
{
if (itemMinPrice != value)
{
itemMinPrice = value;
OnPropertyChanged("ItemMinPrice");
}
}
get
{
return itemMinPrice;
}
}
string itemName;
public string ItemName
{
set
{
if (itemName != value)
{
itemName = value;
OnPropertyChanged("ItemName");
}
}
get
{
return itemName;
}
}
}
Then you can change the bindingContext, like this code BindingContext = new MyModel();
I add a break point in the MyModel, give a itemName in the Entry you can see it was executed like following GIF.
Note: If you want to achieve that model data changed could display the view. You should change Mode to TwoWay like following code.
<Entry PlaceholderColor="White" Keyboard="Chat" Margin="40"
Placeholder="Item Name" TextColor="Black" Text="{Binding ItemName,
Mode=TwoWay}"></Entry>
Here is offical artical about MVVM, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

I'm looking for a more reliable option in Xamarin

I dont know how is it called.
I need to create something that works this way:
Do button, when you click button under button you have list and you can choos one option. List should be button's width.
You can find it in aplication to choose for example language of country.
Do Xamarin built-in something to create this? Or can someone show me how implement this?
Or you could roll your own in Forms, something like:
ImagePickerDropDown.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ImagePickerDropdownSample.ImagePickerDropdown"
x:Name="imagePickerDropDown" >
<ContentView.Content>
<StackLayout>
<ImageButton x:Name="mainButton"
Source="{Binding Source={x:Reference imagePickerDropDown}, Path=SelectedImage}"
Clicked="ImageClicked" />
<StackLayout x:Name="stackView"
BindableLayout.ItemsSource="{Binding Source={x:Reference imagePickerDropDown}, Path=Images}"
IsVisible="False">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<ImageButton Source="{Binding .}" Clicked="ImageSelected"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ContentView.Content>
</ContentView>
ImagePickerDropDown.xaml.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace ImagePickerDropdownSample
{
public partial class ImagePickerDropdown : ContentView
{
public ImagePickerDropdown()
{
InitializeComponent();
}
private void ImageSelected(object sender, EventArgs e)
{
var imageSource = (sender as ImageButton).Source;
SelectedImage = imageSource;
mainButton.IsEnabled = true;
stackView.IsVisible = false;
}
private void ImageClicked(object sender, EventArgs e)
{
mainButton.IsEnabled = false;
stackView.IsVisible = true;
}
public static readonly BindableProperty SelectedImageProperty =
BindableProperty.Create(nameof(SelectedImage), typeof(ImageSource), typeof(ImagePickerDropdown), null);
public ImageSource SelectedImage
{
get
{
return (ImageSource)GetValue(SelectedImageProperty);
}
set
{
SetValue(SelectedImageProperty, value);
}
}
public static readonly BindableProperty ImagesProperty =
BindableProperty.Create(nameof(Images), typeof(ObservableCollection<ImageSource>), typeof(ImagePickerDropdown), null);
public ObservableCollection<ImageSource> Images
{
get
{
return (ObservableCollection<ImageSource>)GetValue(ImagesProperty);
}
set
{
SetValue(ImagesProperty, value);
}
}
}
}
Using it XAML:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="ImagePickerDropdownSample.MainPage"
xmlns:local="clr-namespace:ImagePickerDropdownSample"
Padding="0,50,0,0"
BackgroundColor="Black">
<StackLayout
x:Name="mainLayout">
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="Start"
TextColor="White"/>
<local:ImagePickerDropdown SelectedImage="{Binding SelectedImage}"
Images="{Binding Images}"
WidthRequest="50"
HorizontalOptions="Center"
BackgroundColor="Black"/>
</StackLayout>
</ContentPage>
Using it code behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ImagePickerDropdownSample
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
Images = new ObservableCollection<ImageSource>();
Images.Add(new FileImageSource() { File = "image1.png" });
Images.Add(new FileImageSource() { File = "image2.png" });
Images.Add(new FileImageSource() { File = "image3.png" });
SelectedImage = Images[0];
BindingContext = this;
}
ImageSource _selectedImage;
public ImageSource SelectedImage
{
get
{
return _selectedImage;
}
set
{
if (_selectedImage != value)
{
_selectedImage = value;
OnPropertyChanged(nameof(SelectedImage));
}
}
}
ObservableCollection<ImageSource> _images;
public ObservableCollection<ImageSource> Images
{
get
{
return _images;
}
set
{
if (_images != value)
{
_images = value;
OnPropertyChanged(nameof(Images));
}
}
}
}
}
Use a Spinner .. basically you need to first create an ArrayAdapter then attach the ArrayAdapter to a Spinner :
//we need a List of some type because the ArrayAdapter takes one as param
var items = new List<string>() {"one", "two", "three"};
//instantiate the ArrayAdapter with context, your Resource is a layout, items is the List
var adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleSpinnerItem, items);
//then instantiate your spinner
var spinner = FindViewById<Spinner>(Resource.Id.spinner);
//and attach the adapter to the spinner like this
spinner.Adapter = adapter;
from #Aaron He
Create android spinner dynamically in Xamarin

Changing ViewModel Property, in another class Doesn't Update UI

I'm having this issuse in a more complex App so I built a simple App to test it out on. but first the code, which is an odd layout for a simple app but bare in mind that this mimics my other app.
Note, Baseviewmodel inherits from basemodel.
MainPage
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="TestINotify.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestINotify">
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label HorizontalOptions="Center"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<Label HorizontalOptions="Center"
Text="{Binding LabelTextProperty}"
VerticalOptions="CenterAndExpand" />
<Button Command="{Binding ChangeTextCommand}" Text="Change Label Text" />
</StackLayout>
</ContentPage>
MainPageViewModel
public class MainPageViewModel : BaseViewModel
{
public ICommand ChangeTextCommand { private set; get; }
private string _labelText = "Default text" ;
public string LabelTextProperty
{
get
{
return _labelText;
}
set
{
_labelText = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
ChangeTextCommand = new Command(execute: () =>
{
var handler = new ChangeTextClass();
handler.ChangeTexts();
});
}
}
ChangeTextClass
public class ChangeTextClass
{
public MainPageViewModel mpvm = new MainPageViewModel();
public void ChangeTexts()
{
mpvm.LabelTextProperty = "The Text Was Changed ?";
}
}
BaseModel
public abstract class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
When i assign the label's text value in the viewmodel by
ChangeTextCommand = new Command(execute: () =>
{
LabelTextProperty = "Local works";
});
It works fine but, so maybe it's be something to do with me creating a new instances of the viewmodel in the class ?

How to show data from ASP.NET Web Application to Xamarin.Forms?

I want all the records created in ASP.NET Web Application to be shown in my Mobile App Xamarin.Forms. What's happening to my program is that I was able to create records in my Web Application and save it, but I wasn't able to make it appear in my Xamarin.Forms Mobile app. I have created a MainViewModel that will get the records from the Web Application which I have binded to my MainPage.
These are my codes:
MainPageMain.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinDemoApp"
x:Class="XamarinDemoApp.MainPageMain"
xmlns:ViewModels="clr-namespace:XamarinDemoApp.ViewModels;assembly=XamarinDemoApp"
BackgroundColor="Teal"
Title=" Title Bar">
<ContentPage.BindingContext>
<ViewModels:MainViewModel/>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical">
<ListView ItemsSource="{Binding EmployeesList}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}"
FontSize="24"/>
<Label Text="{Binding Department}"
FontSize="24"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Text="This is the MainPage"/>
</StackLayout>
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinDemoApp.Models;
using XamarinDemoApp.Services;
namespace XamarinDemoApp.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private List<Employee> _employeesList;
public List<Employee> EmployeesList
{
get { return _employeesList; }
set
{
_employeesList = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var employeesServices = new EmployeesServices();
EmployeesList = await employeesServices.GetEmployeesAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Make a separate API controller that you can use to call EmployeeList as a JSON object. That is the preferred way to do this kind of thing. Example:
public class EmployeeApiController : ApiController
{
[HttpGet]
public async Task<List<Employee>> Get()
{
return await employeesServices.GetEmployeesAsync();
}
}

Categories

Resources