How to use ZXing in MVVM? - xamarin

By the moment I am using the scanner in code behind in this way:
View:
<zxing:ZXingScannerView x:Name="ucZXingScannerView" IsScanning="True" IsAnalyzing="True" ScanResultCommand="{Binding CodigoQr, Mode=TwoWay}" OnScanResult="ucZXingScannerView_OnScanResult" />
Code behind:
void ucZXingScannerView_OnScanResult(ZXing.Result result)
{
Device.BeginInvokeOnMainThread(() =>
{
ScannerViewModel miViewModel = this.BindingContext as ScannerViewModel;
miViewModel.CodigoQr = result.Text + " (type: " + result.BarcodeFormat.ToString() + ")";
ucZXingScannerView.IsScanning = false;
ucZXingScannerView.IsAnalyzing = false;
});
}
But I would like to avoid to have code in the code behind, so I am trying this:
View:
<zxing:ZXingScannerView x:Name="ucZXingScannerView" IsScanning="True" IsAnalyzing="True" ScanResultCommand="{Binding CodigoQr, Mode=TwoWay}" OnScanResult="{Binding ResultadoQrCommand}" />
View Model:
public Command<ZXing.Result> ResultadoQrCommand { get; private set; }
private void OnresultadoQr(ZXing.Result paramResultado)
{
CodigoQr = "Prueba";
}
But in this case a get an error in the xaml code that tells "event OnScanResult can only bound to properties of delegate ScanResultDelegate".
My command in the view model has the same parameters that the method of the code behind and I don't be able to find the solution.
How could I define the command in the view model to can bind to the view model?
EDIT: according to the suggestion from Jason, I have tried this code:
The view:
<zxing:ZXingScannerView x:Name="ucZXingScannerView" IsScanning="True" IsAnalyzing="True" ScanResultCommand="{Binding ScanResultCommand}" />
The view model:
public ScannerViewModel()
{
ScanResultCommand = new Command<ZXing.Result>((x) => OnScanResultCommand(x), (x) => true);
}
public Command<ZXing.Result> ScanResultCommand { get; private set; }
private void OnScanResultCommand(ZXing.Result paramResultado)
{
CodigoQr = paramResultado.Text;
}
But the command is not rised.
Thanks.

Related

Xamarin MVVM image viewer with zoom and pan

I have a Xamarin MVVM class that displays a full page image. The code is shown below. The single tap simply closes the view. My question is how to implement the pan or zoom. The only things that I can find are written for Xamarin Forms and I can't figure out how to adapt them to MVVM. Thanks.
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:ImageViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:BLE.Client.ViewModels;assembly=BLE.Client"
x:Class="BLE.Client.Pages.ImagePage" Title="View Image">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackLayout Grid.Row="0" Orientation="Horizontal" >
<Image x:Name="WaypointImage" Source="{Binding MyImage}" HorizontalOptions="FillAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}"/>
<PinchGestureRecognizer Command="{Binding PinchCommand}"/>
<PanGestureRecognizer Command="{Binding PanCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</Grid>
</views:MvxContentPage>
using System;
using System.Windows.Input;
using Xamarin.Forms;
using Acr.UserDialogs;
using MvvmCross;
using MvvmCross.ViewModels;
using MvvmCross.Navigation;
using Plugin.BLE.Abstractions.Contracts;
namespace BLE.Client.ViewModels
{
public class ImageViewModel : BaseViewModel
{
private ImageSource _myImage;
public ImageSource MyImage
{
get => _myImage;
set
{
_myImage = value;
RaisePropertyChanged(() => MyImage);
}
}
private readonly IUserDialogs _userDialogs;
private readonly IMvxNavigationService _navigation;
public String _waypoint;
public Waypoint waypoint;
public ICommand tapCommand;
public ICommand TapCommand {
get { return tapCommand; }
}
public ICommand pinchCommand;
public ICommand PinchCommand {
get { return pinchCommand; }
}
public ICommand panCommand;
public ICommand PanCommand {
get { return panCommand; }
}
public ImageViewModel(IAdapter adapter, IUserDialogs userDialogs) : base(adapter)
{
_userDialogs = userDialogs;
_navigation = Mvx.IoCProvider.Resolve<IMvxNavigationService>();
tapCommand = new Command (OnTapped);
pinchCommand = new Command (OnPinched);
panCommand = new Command (OnPan);
}
void OnTapped (object s) {
Console.WriteLine($"OnTapped: {s}");
_navigation.Close(this);
}
void OnPinched (object s) {
Console.WriteLine($"OnPinched: {s}");
}
void OnPan (object s) {
Console.WriteLine($"OnPan: {s}");
}
public override async void Prepare(MvxBundle parameters)
{
base.Prepare(parameters);
_waypoint = await GetWaypointFromBundleAsync(parameters);
string[] tags = _waypoint?.Split(' ');
char[] trimChars = { 'I', 'D', '=' };
string id = tags[0].TrimStart(trimChars);
int ID = System.Convert.ToInt32(id);
waypoint = await Database.GetWaypointAsync(ID);
MyImage = ImageSource.FromFile(waypoint.FileName);
Console.WriteLine($"prepare ID={waypoint.ID} {waypoint.FileName} ");
}
public override void ViewAppeared()
{
base.ViewAppeared();
if (_waypoint != null)
{
return;
}
_navigation.Close(this);
}
public override void ViewDisappearing()
{
base.ViewDisappearing();
}
public override void ViewDisappeared()
{
base.ViewDisappeared();
}
}
}
Update:
I found an even better solution to the one below:
My answer to a pinch and pan question
This is my previous answer that works to a point but I could not get the pinch and pan gestures to trigger:
I have found an MVVM solution that takes advantage of the bindable properties Image: TranslationX, TranslationY, and Scale. Here are the relevant code changes. I tested this by modifying MyImageTranslationX in the OnTapped method and saw that the image shifts to the right.
Now I just need to get the Image.GestureRecognizers (PinchGestureRecognizer and PanGestureRecognizer) working and then add code that I have seen elsewhere to smoothly pan and zoom.
<Image x:Name="WaypointImage"
Source="{Binding MyImage}"
Scale = "{Binding MyImageScale}"
TranslationX = "{Binding MyImageTranslationX}"
TranslationY = "{Binding MyImageTranslationY}"
HorizontalOptions="FillAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}"/>
<PinchGestureRecognizer Command="{Binding PinchCommand}"/>
<PanGestureRecognizer Command="{Binding PanCommand}"/>
</Image.GestureRecognizers>
</Image>
public double _myImageScale = 1.0;
public double MyImageScale
{
get => _myImageScale;
set
{
_myImageScale = value;
RaisePropertyChanged(() => MyImageScale);
}
}
public double _myImageTranslationX = 0;
public double MyImageTranslationX
{
get => _myImageTranslationX;
set
{
_myImageTranslationX = value;
RaisePropertyChanged(() => MyImageTranslationX);
}
}
public double _myImageTranslationY = 0;
public double MyImageTranslationY
{
get => _myImageTranslationY;
set
{
_myImageTranslationY = value;
RaisePropertyChanged(() => MyImageTranslationY);
}
}
private ImageSource _myImage;
public ImageSource MyImage
{
get => _myImage;
set
{
_myImage = value;
RaisePropertyChanged(() => MyImage);
}
}
void OnTapped (object s) {
Console.WriteLine($"OnTapped: {MyImage}");
MyImageTranslationX = MyImageTranslationX + 100;
// _navigation.Close(this);
}

How to get last-child data Firebase Realtime Database and show in Xamarin

I have a list of room_ retrieved from Firebase Realtime Database. However:
How do I get the last-child data of each of those room_. Like the example in the image below: I want to get the result Key: Mqof3FYCrNdn0ipU5T8 for room_001_002, Key: MqofzCyjqQJ56BLiNHu for room_001_003 and display in CollectionView.
ListRoom.xaml
<CollectionView x:Name="listChats" ItemsSource="{Binding IteamGetRoomChats}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout VerticalOptions="FillAndExpand" Margin="0,0,0,2">
<StackLayout BindableLayout.ItemsSource="{Binding IteamGetRoomChatsChild}">
<Frame x:Name="Tap_chatdetail" HasShadow="False" Padding="12,10" BackgroundColor="#f5f4f4">
<Label Text="{Binding Content}" FontSize="13"/>
<Label Text="{Binding NameUser}" FontSize="13"/>
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding viewdetailChat}" CommandParameter="{Binding RoomID}" />
</Frame.GestureRecognizers>
</Frame>
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
ListRoom.xaml.cs
public ObservableCollection<GetRoomChats> IteamGetRoomChats { get; set; } = new ObservableCollection<GetRoomChats>();
public ObservableCollection<MyDatabaseRecord> IteamGetRoomChatsChild { get; set; } = new ObservableCollection<MyDatabaseRecord>();
List<GetRoomChats> listroomuser = new List<GetRoomChats>();
....
public Command viewdetailChat { get; set; }
....
public ListRoom()
{
LoadContentChat();
}
protected async void LoadContentChat()
{
BindingContext = this;
//Get room user success
var FirebaseClient = fc
.Child("RecordsChat")
.AsObservable<GetRoomChats>()
.Subscribe((dbevent) =>
{
foreach (var room in listroomuser)
{
if (dbevent.Key == room.RoomID)
{
IteamGetRoomChats.Add(dbevent.Object);
}
}
//How to get the last column of each of those room_
});
//End get room user success
viewdetailChat = new Command( async() => await Navigation.PushAsync(new DetailChats(//get roomid)));
}
Models
public class GetRoomChats
{
public string RoomID { get; set; }
}
public class MyDatabaseRecord
{
public string UID { get; set; }
public string NameUser { get; set; }
public string Content { get; set; }
public string RoomID { get; set; }
}
How can I get the roomid to execute the Command without going through any ViewModel.
viewdetailChat = new Command( async() => await Navigation.PushAsync(new DetailChats(//get roomid)));
Thanks for all the help. Thanks
Update
Questions 1:
var FirebaseClient = fc
.Child("RecordsChat")
.AsObservable<GetRoomChats>()
.Subscribe((dbevent) =>
{
foreach (var room in listroomuser)
{
if (dbevent.Key == room.RoomID)
{
IteamGetRoomChats.Add(dbevent.Object);
//Get last-child off room
var query = fc.Child("RecordsChat").Child(dbevent.Key).OrderByKey().LimitToLast(1);
var get = query.AsObservable<MyDatabaseRecord>().Subscribe((dbeventchild) =>
{
IteamGetRoomChatsChild.Add(dbeventchild.Object);//get Null
});
}
}
});

Custom ListView binding not working in Xamarin.Forms

I am trying to bind a response to a custom ListView in Xamarin.Forms but the ListView remains blank even when the ObservableCollection contains results. Please point out my mistake in the code below.
In the below code, I am trying to achieve following result -
The response received from Firebase is serialized and then a custom calendar is created based on the response.
To create the calendar, I am using a Model which will contain all the necessary bindings to be displayed in the View. e.g Color, title, counts etc.
I am using Prism MVVM architecture for building the app.
View
<ListView ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BorderColor="LightGray" Padding="5">
<StackLayout Orientation="Horizontal">
<!--<BoxView BackgroundColor="{Binding HighlightColor}" WidthRequest="10"/>-->
<Label Text="{Binding Date}" FontSize="Medium" Margin="20,0,0,0"/>
<Label Text="{Binding Day}" FontSize="Medium" Margin="20,0,0,0"/>
<Label Text="{Binding LeaveCount}" HorizontalOptions="EndAndExpand" HorizontalTextAlignment="End"/>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<CalendarItem> ListItems { get; set; }
public LeaveViewerPageViewModel(INavigationService navigationService, IFirebaseService firebaseService)
: base(navigationService, firebaseService)
{
Title = "View Leaves";
ViewCommand = new DelegateCommand(ViewLeaves);
}
private async void ViewLeaves()
{
ListItems = new ObservableCollection<CalendarItem>();
var x = await FirebaseService.GetAll(_month, _year);
foreach (var item in x)
{
ListItems.Add(item);
}
}
Service
public async Task<List<CalendarItem>> GetAll( string month, string year)
{
List<CalendarItem> ItemList = new List<CalendarItem>();
int iterationLimit = DateTime.DaysInMonth(int.Parse(year), (DateTime.ParseExact(month.Substring(0, 3), "MMM", CultureInfo.InvariantCulture)).Month);
for (int i = 1; i <= iterationLimit; i++)
{
CalendarItem ITEM = new CalendarItem();
DateTime dateTime = new DateTime(int.Parse(year), DateTime.ParseExact(month.Substring(0, 3), "MMM", CultureInfo.InvariantCulture).Month, i);
var res = await GetLeavesAsync(i, month, year);
ITEM.Date = dateTime.Day.ToString();
ITEM.Day = dateTime.DayOfWeek.ToString();
ITEM.LeaveCount = res.Count;
ITEM.Leaves = res;
if (res.Count > 0)
ITEM.HighlightColor = res.Count < 5 ? System.Drawing.Color.Yellow : System.Drawing.Color.Tomato;
ItemList.Add(ITEM);
}
return ItemList;
}
Model
public class CalendarItem
{
public Color HighlightColor { get; set; }
public string Date { get; set; }
public string Day { get; set; }
public int LeaveCount { get; set; }
public List<LeaveDetail> Leaves { get; set; }
}
Based on the code you have here there are actually a couple of issues:
1) Your ListItems property in the ViewModel is not itself implementing INotifyPropertyChanged.
public class ViewAViewModel : BindableBase
{
private ObservableCollection<CalendarItem> _listItems;
public ObservableCollection<CalendarItem> ListItems
{
get => _listItems;
set => SetProperty(ref _listItems, value);
}
}
2) You are newing up a ListItems in the ViewLeaves method and then adding each item one at a time. The result here is that if you followed the first step when you set ListItems it would try to update the UI, and then every time you add an item it will try to update the UI.
3) You have two options for optimizing this:
Use ObservableRangeCollection which James Montemagno has in his MvvmHelpers library. This will allow you to new up the collection in the ctor and then simply dump a collection in like:
private async void ViewLeaves()
{
ListItems.ReplaceRange(await FirebaseService.GetAll(_month, _year));
}
Simply implement the property as shown in the first issue I pointed out but change it to IEnumerable<CalendarItem> and simply set it like:
private async void ViewLeaves()
{
ListItems = await FirebaseService.GetAll(_month, _year);
}
Please try modifying your ListItems as follows:-
private ObservableCollection<CalendarItem> _listItems = new ObservableCollection<CalendarItem>();
public ObservableCollection<CalendarItem> ListItems
{
get => _listItems;
set => SetProperty(ref _listItems, value);
}

updating progress bar using Task parallel library

I am trying to update the list of progress bars to show the image download progress using Task Parallel Library in Xamarin.Forms
For now I have written a block of code to simulate the download process by using a delay.
Here is my Xaml file where a ListView named MediaList resides with one caption and progress bar per item.
<?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="ImageTask.View.ImageTaskView">
<ContentPage.Content>
<ListView ItemsSource="{Binding MediaList}" CachingStrategy = "RecycleElement" VerticalOptions="FillAndExpand" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text = "{Binding mediaName}" FontSize="22" />
<ProgressBar Progress="{Binding mediaProgress}"></ProgressBar>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
Here is my view model in which I have created one action block that takes the entire list of media object and tries to update the progress bar.
However, my main issue is I am not able to see the progress updated in my UI , so I don't know how shall I update my UI.
public class ImageTaskViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<MediaInfo> _mediaList;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public IList<MediaInfo> MediaList
{
get { return _mediaList; }
set
{
_mediaList = value;
OnPropertyChanged("MediaList");
}
}
public ImageTaskViewModel()
{
Action<IList<MediaInfo>> progressActionBlock = mediaInfoList =>
{
// infinite loop to simulate download
while (true)
{
IEnumerator<MediaInfo> dataList = mediaInfoList.GetEnumerator();
Task.Delay(2000).Wait();
while (dataList.MoveNext())
{
MediaInfo mediaInfo = dataList.Current;
Debug.WriteLine("media name " + mediaInfo.mediaName + " progress " + mediaInfo.mediaProgress);
if (mediaInfo.mediaProgress == 1)
{
Debug.WriteLine("media name " + mediaInfo.mediaName + " Done ");
break;
}
else
{
mediaInfo.mediaProgress = mediaInfo.mediaProgress + 0.1;
}
}
}
};
var opts = new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 2
};
var progressAction = new ActionBlock<IList<MediaInfo>>(progressActionBlock, opts);
MediaList = new List<MediaInfo>();
for (int i = 1; i < 6; i++)
{
MediaInfo mediaInfo = new MediaInfo();
mediaInfo.mediaName = i.ToString();
MediaList.Add(mediaInfo);
}
// Exectue Action block
progressAction.Post(MediaList);
}
}
Model of MediaInfo:
public class MediaInfo
{
public string mediaId { get; set; }
public string mediaName { get; set; }
public string mediaPath { get; set; }
public byte[] mediaStream { get; set; }
public double mediaProgress { get; set; } = 0.1;
}
In your View model you don't have mediaProgress. You should add
Double _mediaProgress;
public Double mediaProgress{
get{
return _mediaProgress;
} set{
_mediaProgress = value;
OnPropertyChanged("Email");
}
}
And then in your all method you use mediaProgress to set the progress.

Async loading of images

I want to use best way to show images in my PanoramaPage. I download one page and shows it´s information a then I want to async load another page with images. So I am using HttpWebRequest and I get response. Everything is okay and hope this is best way for these. So I create my GaleryViewModel and for all images at page I add url to my class.
And there is a problem. I can´t see images in view. This i my view:
<ListBox ItemsSource="{Binding Images}" x:Name="listImages" Height="652" Canvas.Top="80">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<Image Height="100" Width="100" Margin="12,0,9,0" >
<Image.Source>
<BitmapImage UriSource="{Binding ImgURL}" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is content of my WebResponse event handler:
MovieExt movie = this.DataContext as MovieExt;
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(response);
var photos = from ti in doc.DocumentNode.Descendants("div")
where ti.Attributes["class"] != null && ti.Attributes["class"].Value == "photo"
select ti;
Regex rgx = new Regex("http://[0-9a-zA-Z_./]+");
foreach (var photo in photos)
{
GaleryViewModel fotka = new GaleryViewModel();
string style = photo.Attributes["style"].Value;
MatchCollection matches = rgx.Matches(style);
if (matches.Count > 0)
{
foreach (Match match in matches)
fotka.ImgURL = match.Value;
}
fotka.LineOne = "Test";
movie.Images.Add(fotka);
}
this.DataContext = movie;
this.listImages.ItemsSource = movie.Images;
and for all GaleryViewModel and MovieExt:
public class GaleryViewModel : INotifyPropertyChanged
{
private string _imgUrl;
public string ImgURL
{
get
{
return _imgUrl;
}
set
{
if (value != _imgUrl)
{
_imgUrl = value;
NotifyPropertyChanged("ImgURL");
}
}
}
private string _lineOne;
public string LineOne
{
get
{
return _lineOne;
}
set
{
if (value != _lineOne)
{
_lineOne = value;
NotifyPropertyChanged("LineOne");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MovieExt
{
public string Title { get; set; }
public string Year { get; set; }
public string Url { get; set; }
...
public List<GaleryViewModel> Images { get; set; }
...
}
I am not sure what I am doing wrong but I think that is something with binding. Thanks for help
Looking at this it looks like you haven't notified that the MovieExt.Images property has changed. Without doing this your ListBox won't update its Items. To do this MovieExt will also need to support INotifyPropertyChanged.
You have to convert Uri to BitmapImage, because BitmapImage UriSource is a stream. Please take a look at this: Image UriSource and Data Binding
Replace loading method (just for test) to:
MovieExt movie = new MovieExt();
movie.Images = new List<GaleryViewModel>();
GaleryViewModel fotka1 = new GaleryViewModel();
fotka1.LineOne = "line1";
fotka1.ImgURL = "http://proservice.kiev.ua/wp-content/uploads/2011/10/apple-logo.jpg";
GaleryViewModel fotka2 = new GaleryViewModel();
fotka2.LineOne = "line2";
fotka2.ImgURL = "http://www.mykhailenko.com/blog/wp-content/uploads/2010/12/apple-logo-history-2.png";
movie.Images.Add(fotka1);
movie.Images.Add(fotka2);
listImages.ItemsSource = movie.Images;
and it works
I guess the problem is in this line:
if (matches.Count > 0)
You have no matches, so your Url is empty
Can you provide us data your service return to debug code in your context?
Also, why you need loop for this assignment?
foreach (Match match in matches)
fotka.ImgURL = match.Value;

Resources