Binding MicroCharts in Xamarin - c#

I tried to make a chart with MicroChart, by LearnTechnologies youtube tutorial (https://www.youtube.com/watch?v=O6xhZkJa7H4&t=1s), but for me it's not working. I tried to do it completely based on the video, but it still doesn't work. In emulator the chart not showing. I think the problem is with the binding, the MainPage xaml indicates that the {Binding LineChart} "No DataContext found for Binding 'LineChart'.
My code:
namespace MicroChartBinding.ViewModels
{
public class ViewModelBase : BindableBase, IInitialize, INavigationAware, IDestructible
{
protected INavigationService NavigationService { get; private set; }
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
}
public virtual void Initialize(INavigationParameters parameters)
{
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
}
}
namespace MicroChartBinding.ViewModels
{
class MainPageViewModel : ViewModelBase
{
private LineChart lineChart;
public LineChart LineChart
{
get => lineChart;
set => SetProperty(ref lineChart, value);
}
private string[] months = new string[] { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
private float[] turnoverData = new float[] { 6010, 1000, 2524, 3245, 1245, 5767, 5544, 5242, 500, 1555, 400, 100 };
private SKColor blueColor = SKColor.Parse("#09C");
public MainPageViewModel(INavigationService navigationService)
: base(navigationService)
{
Title = "Main Page";
}
public override void Initialize(INavigationParameters parameters)
{
base.Initialize(parameters);
InitData();
}
private void InitData()
{
var turnoverEntries = new List<ChartEntry>();
foreach (var data in turnoverData)
{
turnoverEntries.Add(new ChartEntry(data)
{
Color = blueColor,
ValueLabel = $"{data / 1000} k",
Label = "trnover"
});
}
LineChart = new LineChart { Entries = turnoverEntries, LabelTextSize = 30f, LabelOrientation = Orientation.Horizontal };
}
}
}
<?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:Charts="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
x:Class="MicroChartBinding.MainPage"
Title="{Binding Title}">
<Grid RowDefinitions="300,300,300">
<Charts:ChartView Chart ="{Binding LineChart}"
HeightRequest="300"
VerticalOptions="Start"
HorizontalOptions="Fill"/>
</Grid>
</ContentPage>

The tutorial you shared use nuget Prism.Unity.Forms, so you need to modify your code App.xaml.cs and App.xaml as follows,which implement the bind for the ViewModel and the page.
App.xaml.cs
public partial class App
{
public App(IPlatformInitializer initializer): base(initializer)
{
}
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/MainPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
}
}
App.xaml
<?xml version="1.0" encoding="utf-8" ?>
<prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
x:Class="ChartAppForm.App">
<Application.Resources>
</Application.Resources>
</prism:PrismApplication>
And modify code in android platform as follows:
[Activity(Label = "ChartAppForm", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
//LoadApplication(new App());
LoadApplication(new App(new AndroidInitializer()));
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// Register any platform specific implementations
}
}
Note:
You can get the sample of above tutorial here .

Related

Xamarin.Forms ListView continually creates ViewCells

I have this tiny demo that appears to show that ListView continually creates ViewCells, and binds them to the one view model, until it runs out of memory. Here's the code...
From MainPage.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"
x:Class="LVD.MainPage">
<StackLayout>
<Label Text="This is a listview with one item."/>
<Label Text="{Binding MainNum}"/>
<ListView x:Name="DemoList"
ItemsSource="{Binding Itms}"
RowHeight="75"
CachingStrategy="RecycleElement">
</ListView>
</StackLayout>
</ContentPage>
MainPage.xaml.cs:
public partial class MainPage : ContentPage
{
MainVM _vm = null;
public MainPage()
{
InitializeComponent();
_vm = new MainVM();
BindingContext = _vm;
DemoList.ItemTemplate = new DataTemplate(typeof(myCell));
}
}
MainVM.cs:
using System.Collections.ObjectModel;
namespace LVD
{
class MainVM
{
public string MainNum { get; set; }
private static int _mainserno = 0;
public ObservableCollection<CellVM> Itms { get; set; }
public MainVM()
{
MainNum = "Parent serial#: " + _mainserno.ToString();
_mainserno++;
if (Itms == null)
Itms = new ObservableCollection<CellVM>();
else
Itms.Clear();
CellVM item = new CellVM();
Itms.Add(item);
//CellVM item2 = new CellVM();
//Itms.Add(item2);
}
}
}
myCell.cs:
using Xamarin.Forms;
namespace LVD
{
class myCell : ViewCell
{
static int _localserialno = 0;
Label VMnumLbl, CellNumLbl, PLbl;
public static readonly BindableProperty ItemProperty =
BindableProperty.Create("CellItem", typeof(CellVM), typeof(myCell), null);
public CellVM CellItem
{
get { return (CellVM)GetValue(ItemProperty); }
set { SetValue(ItemProperty, value); }
}
public myCell()
{
VMnumLbl = new Label();
VMnumLbl.SetBinding(Label.TextProperty, "VMSerNum", BindingMode.Default, null, "VM Serial #: {0}");
CellNumLbl = new Label();
CellNumLbl.SetBinding(Label.TextProperty, "CellSerNum", BindingMode.Default, null, "Cell Serial #: {0}");
PLbl = new Label();
PLbl.SetBinding(Label.TextProperty, "PingCount", BindingMode.Default, null, "Pings: {0}");
var stackit = new StackLayout();
stackit.Children.Add(VMnumLbl);
stackit.Children.Add(CellNumLbl);
stackit.Children.Add(PLbl);
View = stackit;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext == null)
CellItem = null;
else if (BindingContext != CellItem)
{
CellItem = (CellVM)BindingContext;
CellItem.CellSerNum = _localserialno++;
CellItem.Ping();
}
}
}
}
CellVM.cs:
class CellVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private static int serialno = 0;
private int _vmserialno;
public int VMSerNum { get { return _vmserialno; } }
public int CellSerNum { get; set; }
public int PingCount { get; set; }
public CellVM()
{
_vmserialno = serialno++;
PingCount = 0;
}
public void Ping()
{
PingCount++;
OnPropertyChanged(string.Empty);
}
}
When I run this code, the display looks like this:
This is a listview with one item.
Parent serial #: 0
VM Serial #: 0
Cell Serial #: ++
Pings: ++
The bottom two numbers continually increase. That means the listview recreates the CellView, and binds them all to the same viewmodel. If I put two items in the list, both cells have running Cell Serial #'s and Pings.
Since I couldn't find anyone else complaining about this, I must be doing something wrong. Can anyone enlighten me to what that might be?
advTHANKSance (pun intended)

ListView not saving changes after updating its screen

every time i update my screen (change rotation or leave the screen and then go back to it) it doesn't save the updated list and i go back to the default list. thank you in advance <3
MovieActivity:
public class MovieActivity : Activity, ListView.IOnItemClickListener, Android.Views.View.IOnClickListener, ListView.IOnItemLongClickListener
{
public static List<Movie> movieList { get; set; }
MovieAdapter movieAdapter;
ListView lv;
Button btnAdd;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.movie_page);
// Create your application here
btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
Bitmap vannila_sky_pic = Android.Graphics.BitmapFactory.DecodeResource(Application.Context.Resources, Resource.Drawable.Vanilla_Sky);
Bitmap jimmyneutron_pic = Android.Graphics.BitmapFactory.DecodeResource(Application.Context.Resources, Resource.Drawable.Jimmy_Neutron_Boy_Genius_film);
Movie m1 = new Movie(90, "Vanilla Sky", "James", vannila_sky_pic);
Movie m2 = new Movie(124, "jimmy neutron", "arthur", jimmyneutron_pic);
movieList = new System.Collections.Generic.List<Movie>();
movieList.Add(m1);
movieList.Add(m2);
movieAdapter = new MovieAdapter(this, movieList);
lv = FindViewById<ListView>(Resource.Id.lv);
lv.Adapter = movieAdapter;
lv.OnItemClickListener = this;
lv.OnItemLongClickListener = this;
btnAdd.SetOnClickListener(this);
}
protected override void OnResume()
{
base.OnResume();
if (movieAdapter != null)
{
movieAdapter.NotifyDataSetChanged();
}
}
public void OnItemClick(AdapterView parent, View view, int position, long id)
{
Intent intent = new Intent(this, typeof(EditMovieActivity));
Movie temp = MovieActivity.movieList[position];
intent.PutExtra("pos", position);
StartActivity(intent);
}
public void OnClick(View v)
{
if(v == btnAdd)
{
Intent intent = new Intent(this, typeof(EditMovieActivity));
StartActivity(intent);
}
}
public bool OnItemLongClick(AdapterView parent, View view, int position, long id)
{
MovieActivity.movieList.RemoveAt(position);
movieAdapter.NotifyDataSetChanged();
return true;
}
}
EditMovieActivity:` public class EditMovieActivity : Activity, Android.Views.View.IOnClickListener
{
Button btnSave, btnAddPic;
EditText etTitle, etDirector, etAvg, etLength;
Bitmap bitmap;
int pos = -1;
ImageView iv;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.edit_movie);
iv = FindViewById<ImageView>(Resource.Id.ivMovie);
btnSave = FindViewById<Button>(Resource.Id.btnSave);
btnAddPic = FindViewById<Button>(Resource.Id.btnAddPic);
etTitle = FindViewById<EditText>(Resource.Id.etTitle);
etDirector = FindViewById<EditText>(Resource.Id.etDirector);
etAvg = FindViewById<EditText>(Resource.Id.etAvg);
etLength = FindViewById<EditText>(Resource.Id.etLength);
pos = Intent.GetIntExtra("pos", -1);//-1 is default
btnAddPic.SetOnClickListener(this);
btnSave.SetOnClickListener(this);
if (pos != -1)// update
{
Movie temp = MovieActivity.movieList[pos];
Toast.MakeText(this, "position is " + pos, ToastLength.Long).Show();
etTitle.Text = temp.getTitle();
etDirector.Text = temp.getDirector();
etAvg.Text = "" + temp.getAvg();
etLength.Text = "" + temp.getLength();
bitmap = temp.getBitmap();
iv.SetImageBitmap(temp.getBitmap());
}
else // new movie
{
Toast.MakeText(this, "lets add new item ", ToastLength.Long).Show();
}
}
public void OnClick(View v)
{
if (v == btnAddPic)
{
Intent intent = new Intent(Android.Provider.MediaStore.ActionImageCapture);
StartActivityForResult(intent, 0);
}
else
if (v == btnSave)
{
int length = int.Parse(etLength.Text);
int avg = int.Parse(etAvg.Text);
string title = etTitle.Text;
string director = etDirector.Text;
Movie m = null;
if (pos != -1)//updates
{
m = new Movie(length, title, director, bitmap);
MovieActivity.movieList[pos] = m;
Finish();
}
else// adds new movie
{
m = new Movie(length, title, director, bitmap);
MovieActivity.movieList.Add(m);
Finish();
}
}
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 0)//coming from camera
{
if (resultCode == Result.Ok)
{
bitmap = (Android.Graphics.Bitmap)data.Extras.Get("data");
iv.SetImageBitmap(bitmap);
}
}
}
}`
Movie:
public class Movie
{
private int length;
private string title;
private string director;
private int avg;
private Android.Graphics.Bitmap bitmap;
public Movie(int length, string title, string director,Android.Graphics.Bitmap bitmap )
{
this.length = length;
this.title = title;
this.director = director;
this.avg = 0;
this.bitmap = bitmap;
}
public void setBitmap(Android.Graphics.Bitmap bitmap)
{
this.bitmap = bitmap;
}
public Android.Graphics.Bitmap getBitmap()
{
return bitmap;
}
public void setAvg(int avg)
{
this.avg = avg;
}
public int getAvg()
{
return avg;
}
public void setDirector(string director)
{
this.director = director;
}
public string getDirector()
{
return director;
}
public void setTitle(string title)
{
this.title = title;
}
public string getTitle()
{
return title;
}
public void setLength(int length)
{
this.length = length;
}
public int getLength()
{
return length;
}
}
MovieAdapter:
class MovieAdapter:BaseAdapter<Movie>
{
Android.Content.Context context;
List<Movie> objects;
public MovieAdapter(Android.Content.Context context, System.Collections.Generic.List<Movie> objects)
{
this.context = context;
this.objects = objects;
}
public List<Movie> GetList()
{
return this.objects;
}
public override long GetItemId(int position)
{
return position;
}
public override int Count
{
get { return this.objects.Count; }
}
public override Movie this[int position]
{
get { return this.objects[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
Android.Views.LayoutInflater layoutInflater = ((MovieActivity)context).LayoutInflater;
Android.Views.View view = layoutInflater.Inflate(Resource.Layout.movie_layoutxml, parent, false);
TextView tvTitle = view.FindViewById<TextView>(Resource.Id.tvTitle);
TextView tvDirector = view.FindViewById<TextView>(Resource.Id.tvDirector);
TextView tvLength = view.FindViewById<TextView>(Resource.Id.tvlength);
TextView tvAvg = view.FindViewById<TextView>(Resource.Id.tvAvg);
ImageView ivCover = view.FindViewById<ImageView>(Resource.Id.ivCover);
Movie temp = objects[position];
if(temp != null)
{
ivCover.SetImageBitmap(temp.getBitmap());
tvAvg.Text = "" + temp.getAvg();
tvTitle.Text = temp.getTitle();
tvLength.Text ="" + temp.getLength();
tvDirector.Text = temp.getDirector();
}
return view;
}
}
According to your description, listview don't update when you change some listview item.
I do one sample that you can take a look, create public static List and ListAdapter.
public class MainActivity : AppCompatActivity
{
public static List<Earthquake> earthquakes;
ListView lvw;
public static EarthquakeListAdapter listadapter;
Button btnadd;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
earthquakes = new List<Earthquake>
{
new Earthquake{Place="Nepal", DateOccured="May 12, 2015" },
new Earthquake{Place="Solomon Sea", DateOccured="May 7, 2015" },
new Earthquake{Place="Papua New Guinea", DateOccured="May 5, 2015" },
new Earthquake{Place="Nepal", DateOccured="April 25, 2015" },
new Earthquake{Place="Taiwan", DateOccured="April 20, 2015" },
new Earthquake{Place="Papua New Guinea", DateOccured="March 29, 2015" },
new Earthquake{Place="Flores Sea", DateOccured="Febdruary 27, 2015" },
new Earthquake{Place="Mid-Atlantic range", DateOccured="Febdruary 13, 2015" }
};
lvw = FindViewById<ListView>(Resource.Id.lvwEarthquakes);
btnadd = FindViewById<Button>(Resource.Id.button1);
btnadd.Click += Btnadd_Click;
listadapter= new EarthquakeListAdapter(this, earthquakes);
lvw.Adapter = listadapter;
lvw.ItemLongClick += Lvw_ItemLongClick; ;
lvw.ItemClick += Lvw_ItemClick;
}
protected override void OnResume()
{
Log.Debug("OnResume", "OnResume called, app is ready to interact with the user");
base.OnResume();
listadapter = new EarthquakeListAdapter(this, earthquakes);
lvw.Adapter = listadapter;
}
private void Lvw_ItemLongClick(object sender, AdapterView.ItemLongClickEventArgs e)
{
var item = earthquakes[e.Position];
MainActivity.earthquakes.Remove(item);
List<Earthquake> items = MainActivity.earthquakes;
listadapter = new EarthquakeListAdapter(this, earthquakes);
lvw.Adapter = listadapter;
}
private void Lvw_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
Intent intent = new Intent(this, typeof(editActivity));
//Earthquake temp = MainActivity.earthquakes[e.Position];
string position = e.Position.ToString();
intent.PutExtra("p", position);
StartActivity(intent);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class Earthquake
{
public string Place { get; set; }
public string DateOccured { get; set; }
}
public class EarthquakeListAdapter : BaseAdapter<Earthquake>
{
List<Earthquake> earthquakes;
Activity context;
/// <summary>
/// Constructor
/// </summary>
/// <param name="context"></param>
/// <param name="earthquakeList"></param>
public EarthquakeListAdapter(Activity currentContext, List<Earthquake> earthquakeList) : base()
{
this.earthquakes = earthquakeList;
this.context = currentContext;
}
public override Earthquake this[int position]
{
get { return earthquakes.ToArray()[position]; }
}
public override int Count
{
get { return earthquakes.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem2, null);
view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = earthquakes.ToArray()[position].Place;
view.FindViewById<TextView>(Android.Resource.Id.Text2).Text = earthquakes.ToArray()[position].DateOccured;
}
return view;
}
}
public class editActivity : Activity
{
EditText edittext1;
EditText edittext2;
int position;
Button button;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.editlayout1);
// Create your application here
edittext1 = FindViewById<EditText>(Resource.Id.editText1);
edittext2 = FindViewById<EditText>(Resource.Id.editText2);
button = FindViewById<Button>(Resource.Id.button1);
button.Click += Butto1_Click;
string text = Intent.GetStringExtra("p");
position = Convert.ToInt32(text);
edittext1.Text = MainActivity.earthquakes[position].Place;
edittext2.Text = MainActivity.earthquakes[position].DateOccured;
}
private void Butto1_Click(object sender, EventArgs e)
{
MainActivity.earthquakes[position] = new Earthquake() { Place = edittext1.Text, DateOccured = edittext2.Text };
OnBackPressed();
}
}

Change in collection won't update LiveCharts plot

Changes in MainWindows.xaml.cs DropImages won't update LiveCharts, meaning that after initializing dropPhotos1 in public MainWindow(), changes from ChooseReference_OnClick(object sender, RoutedEventArgs e) won't affect plot despite the fact that values from scatterplot.xaml.cs get updated.
I have MainWindow.xaml.cs:
namespace DDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<DropPhoto> DropImages = new ObservableCollection<DropPhoto>();
public static readonly DependencyProperty dropPhotos1Property =
DependencyProperty.Register("dropPhotos1", typeof(ObservableCollection<DropPhoto>), typeof(MainWindow), new FrameworkPropertyMetadata(null));
ObservableCollection<DropPhoto> dropPhotos1
{
get { return (ObservableCollection<DropPhoto>)GetValue(dropPhotos1Property); }
set { SetValue(dropPhotos1Property, value); }
}
public MainWindow()
{
InitializeComponent();
Photos.ItemsSource = DropImages;
dropPhotos1 = DropImages;
}
private void ChooseReference_OnClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Jpeg files (*.jpg)|*.jpg|All files (*.*)|*.*";
openFileDialog.Multiselect = false;
openFileDialog.AddExtension = true;
if (openFileDialog.ShowDialog() == true)
{
Properties.Settings.Default.Reference = openFileDialog.FileName;
ReferenceImage.Source = new BitmapImage(new Uri(openFileDialog.FileName));
}
DropImages.Add(new DropPhoto
{
Drop = new Drop()
{
RadiusInMeters = 11
}
});
}
}
I imported plot user control into MainWindow.Xaml:
<uc:ScatterPlot VovaPhotos = "{Binding dropPhotos1, ElementName=myWindow}"/>
scatter plot xaml:
<UserControl x:Class="DDrop.Controls.ScatterPlot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DDrop.Controls"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance local:ScatterPlot}">
<Grid>
<lvc:CartesianChart Name="а" Series="{Binding SeriesCollection}" LegendLocation="Right" >
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="Радиус, м" LabelFormatter="{Binding YFormatter}"></lvc:Axis>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Время, с" Labels="{Binding Labels}"></lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
</UserControl>
scatterplot.xaml.cs:
namespace DDrop.Controls
{
/// <summary>
/// Логика взаимодействия для UserControl1.xaml
/// </summary>
public partial class ScatterPlot : UserControl
{
public static readonly DependencyProperty VovaProperty =
DependencyProperty.Register("VovaPhotos", typeof(ObservableCollection<DropPhoto>), typeof(ScatterPlot));
public ObservableCollection<DropPhoto> VovaPhotos
{
get { return (ObservableCollection<DropPhoto>)GetValue(VovaProperty); }
set { SetValue(VovaProperty, value); }
}
public ScatterPlot()
{
InitializeComponent();
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
Loaded += SudokuUniGrid_Loaded;
}
private void SudokuUniGrid_Loaded(object sender, RoutedEventArgs e)
{
ChartValues<double> values = new ChartValues<double>();
foreach (var dropPhoto in VovaPhotos)
{
values.Add(dropPhoto.Drop.RadiusInMeters);
}
SeriesCollection = new SeriesCollection
{
new LineSeries
{
Title = "Series 1",
Values = values,
LineSmoothness = 0, //0: straight lines, 1: really smooth lines
PointGeometry = Geometry.Parse("m 25 70.36218 20 -28 -20 22 -8 -6 z"),
PointGeometrySize = 50,
PointForeground = Brushes.Gray
},
};
Labels = new[] { "Jan", "Feb", "Mar", "Apr", "May" };
YFormatter = value => value.ToString("C");
а.Update();
DataContext = this;
}
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Func<double, string> YFormatter { get; set; }
}
}
DropPhoto model:
using System.ComponentModel;
namespace DDrop.BE.Models
{
public class DropPhoto : INotifyPropertyChanged
{
private Drop _drop;
public Drop Drop
{
get
{
return _drop;
}
set
{
_drop = value;
OnPropertyChanged(new PropertyChangedEventArgs("Drop"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
}
Drop model:
using System;
using System.ComponentModel;
namespace DDrop.BE.Models
{
public class Drop : INotifyPropertyChanged
{
private double _radiusInMeters;
public double RadiusInMeters
{
get
{
return _radiusInMeters;
}
set
{
_radiusInMeters = value;
OnPropertyChanged(new PropertyChangedEventArgs("RadiusInMeters"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
}
I managed to do update them, by implementing INotifyPropertyChanged
using DDrop.BE.Models;
using LiveCharts;
using LiveCharts.Wpf;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace DDrop.Controls
{
/// <summary>
/// Логика взаимодействия для UserControl1.xaml
/// </summary>
public partial class ScatterPlot : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty VovaProperty =
DependencyProperty.Register("VovaPhotos", typeof(ObservableCollection<DropPhoto>), typeof(ScatterPlot));
public ObservableCollection<DropPhoto> VovaPhotos
{
get { return (ObservableCollection<DropPhoto>)GetValue(VovaProperty); }
set { SetValue(VovaProperty, value); OnPropertyChanged(new PropertyChangedEventArgs("VovaPhotos")); }
}
public ScatterPlot()
{
InitializeComponent();
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
Loaded += SudokuUniGrid_Loaded;
}
private void SudokuUniGrid_Loaded(object sender, RoutedEventArgs e)
{
ChartValues<double> values = new ChartValues<double>();
foreach (var dropPhoto in VovaPhotos)
{
values.Add(dropPhoto.Drop.RadiusInMeters);
}
SeriesCollection = new SeriesCollection
{
new LineSeries
{
Title = "Series 1",
Values = values,
LineSmoothness = 0, //0: straight lines, 1: really smooth lines
PointGeometry = Geometry.Parse("m 25 70.36218 20 -28 -20 22 -8 -6 z"),
PointGeometrySize = 50,
PointForeground = Brushes.Gray
},
};
Labels = new[] { "Jan", "Feb", "Mar", "Apr", "May" };
YFormatter = value => value.ToString("C");
а.Update();
DataContext = this;
}
private SeriesCollection _series;
public SeriesCollection SeriesCollection
{
get
{
return _series;
}
set
{
_series = value;
OnPropertyChanged(new PropertyChangedEventArgs("SeriesCollection"));
}
}
public string[] Labels { get; set; }
public Func<double, string> YFormatter { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
}

Capturing and updating an image source using MVVM in Xamarin

I'm trying to capture photo and display the captured image in Xamarin but changing the image source binding just doesn't seem to work. This seems really simple so I'm not quite sure where I'm going wrong.
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private string _imageSource;
public string ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
SetProperty(ref _imageSource, value);
}
}
public DelegateCommand TakePhotoCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService)
: base(navigationService)
{
Title = "Main Page";
_dialogService = pageDialogService;
TakePhotoCommand = new DelegateCommand(TakePhoto);
}
async void TakePhoto()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await _dialogService.DisplayAlertAsync("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
// This does get called ok
ImageSource = file.Path;
}
}
ViewModelBase.cs
public class ViewModelBase : BindableBase, INavigationAware, IDestructible
{
protected INavigationService NavigationService { get; private set; }
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
}
public virtual void OnNavigatedFrom(NavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(NavigationParameters parameters)
{
}
public virtual void OnNavigatingTo(NavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
}
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PhotoTesting.Views.MainPage"
Title="{Binding Title}">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Image Source="{Binding ImageSource}" WidthRequest="200" HeightRequest="200" Aspect="AspectFill" />
<Button x:Name="CameraButton" Text="Take Photo" Command="{Binding TakePhotoCommand}" />
</StackLayout>
</ContentPage>
I know the image capture bit is working ok, the problem just seems to be setting the image.source after the page has loaded.
You need to bind the Source property of Image to an ImageSource in MainPage.xaml
The ImageSource object can be obtained from the file stream. Here is the code:
public class MainPageViewModel : ViewModelBase
{
private ImageSource _imageSource;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
SetProperty(ref _imageSource, value);
}
}
public DelegateCommand TakePhotoCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService)
: base(navigationService)
{
Title = "Main Page";
_dialogService = pageDialogService;
TakePhotoCommand = new DelegateCommand(TakePhoto);
}
async void TakePhoto()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await _dialogService.DisplayAlertAsync("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
// Here is the problem
//ImageSource = file.Path;
// This is the fix
ImageSource = ImageSource.FromStream(() => file.GetStream());
}
}

Key-Value Observing not triggering on a UIViewController subclass?

I'm trying to get notified when the title of a UIViewController changes.
I tried adding an observer to the title of a UIViewController subclass but it never gets triggered. What's strange about this, is that it works on a plain UIViewController. Am I doing something wrong?
Here's a code example explaining my issue (Xamarin.iOS C#):
using System;
using UIKit;
using System.Collections.Generic;
namespace ObserverTests
{
public partial class ViewController : UIViewController
{
List<UIViewController> viewControllers = new List<UIViewController>();
public override void ViewDidLoad()
{
UIViewController controller1 = new UIViewController() { Title = "Controller1" };
UIViewController controller2 = new Test() { Title = "Controller2" };
this.viewControllers.Add(controller1);
this.viewControllers.Add(controller2);
foreach(UIViewController viewController in viewControllers)
{
viewController.AddObserver("title", Foundation.NSKeyValueObservingOptions.New, (changes) =>
{
Console.WriteLine(viewController.Title);
Console.WriteLine("Title Changed!");
});
}
controller1.Title = "TitleChanged1"; // Works
controller2.Title = "TitleChanged2"; // Fails
}
private class Test : UIViewController
{
}
}
}
In Xamarin the best way might be using inheritance and adding such a feature. For this you derive from UIViewController
public class UIObserveTitleChangedViewController : UIViewController
{
public event TitleChangedEvent TitleChanged;
public override string Title
{
get
{
return base.Title;
}
set
{
var oldTitle = base.Title;
if (oldTitle == value)
return;
base.Title = value;
OnTitleChanged(new TitleChangedEventArgs(value, oldTitle));
}
}
protected virtual void OnTitleChanged(TitleChangedEventArgs args)
{
TitleChanged?.Invoke(this, args);
}
#region ctor
public UIObserveTitleChangedViewController() { }
public UIObserveTitleChangedViewController(NSCoder coder) : base(coder) { }
protected UIObserveTitleChangedViewController(NSObjectFlag t) : base(t) { }
protected internal UIObserveTitleChangedViewController(IntPtr handle) : base(handle) { }
public UIObserveTitleChangedViewController(string nibName, NSBundle bundle) : base(nibName, bundle) { }
#endregion
}
and implement missing event types
public delegate void TitleChangedEvent(object sender, TitleChangedEventArgs args);
public class TitleChangedEventArgs : EventArgs
{
public string NewTitle { get; set; }
public string OldTitle { get; set; }
public TitleChangedEventArgs(string newTitle, string oldTitle)
{
NewTitle = newTitle;
OldTitle = oldTitle;
}
}
You can then subscribe to this event and get notified of changes
public partial class ViewController : UIObserveTitleChangedViewController
{
public ViewController(IntPtr handle) : base(handle)
{
this.TitleChanged += ViewController_TitleChanged; // Subscribe to TitleChanged
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
Title = "Some title"; // triggers TitleChanged
Title = "Another new title"; // triggers TitleChanged
}
private void ViewController_TitleChanged(object sender, TitleChangedEventArgs args)
{
Debug.WriteLine("Title changed from {0} to {1}", args.OldTitle, args.NewTitle);
}
}
You could set the title like so and it will work:
controller2.SetValueForKey(new NSString("TitleChangedHAHA"), new NSString("title"));
You could do this. First define a new event argument that will hold the new title when it changes.
public class TitleChangedEventArgs: EventArgs
{
public string Title { get; private set; }
public TitleChangedEventArgs(string title)
{
Title = title;
}
}
In your test class, add a new Event Handler for TitleChanged and override Title to raise an event when the new title for the view controller.
public class Test : UIViewController
{
public event EventHandler<TitleChangedEventArgs> TitleChanged;
public override string Title {
get {
return base.Title;
}
set {
base.Title = value;
OnTitleChanged();
}
}
public virtual void OnTitleChanged()
{
if (TitleChanged != null) {
TitleChanged.Invoke(this, EventArgs.Empty);
}
}
}
and finally in your Main View Controller you can do something like this:
public class ViewController : UIViewController
{
private Test _test;
public override void ViewDidLoad()
{
_test = new Test();
base.ViewDidLoad();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
_test.TitleChanged += Test_TitleChanged;
}
public override void ViewDidDisappear(bool animated)
{
_test.TitleChanged -= Test_TitleChanged;
base.ViewDidDisappear(animated);
}
void Test_TitleChanged(object sender, TitleChangedEventArgs e)
{
Console.WriteLine("Title Changed! " + e.Title);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
}
}
The Title property in UIViewController is marked virtual...so as an alternative solution, you could define a BaseViewController class and override Title and call a method in the Setter:
Public class BaseViewController : UIViewController
{
....
public override string Title {
get {
return base.Title;
}
set {
base.Title = value;
OnTitleChanged();
}
}
protected virtual void OnTitleChanged()
{
......
}
}
Then you can override OnTitleChanged on any of your UIViewControllers to have a callback when the Title is changed.

Categories