How to watch a MediaPlayer and get feedback on it's CurrentProgress? - c#

I'm writing an android app that uses the MediaPlayer. I've created a custom IAudioPlayer as a wrapper for the MediaPlayer so that I can eventually extend it to iOS.
public interface IAudioPlayer
{
bool IsPlaying { get; }
void Play(string fileName, int startingPoint);
void Play(string fileName);
void Pause();
void Stop();
int CurrentPosition();
bool HasFile();
void SkipForward(int seconds);
void SkipBackward(int seconds);
void SeekTo(int seconds);
}
Our app is structured using the Mvvm pattern and is using MvvmCross.
On our FragmentViewModel we've got commands such as IPlayCommand, IStopCommand, etc.
public class HomeFragmentViewModel : ViewModelBase
{
public HomeFragmentViewModel(IPlayCommand playCommand,
IStopCommand stopCommand,
ISkipForwardCommand skipForwardCommand,
ISkipBackwardCommand skipBackwardCommand)
{
_playCommand = playCommand;
_stopCommand = stopCommand;
_skipForwardCommand = skipForwardCommand;
_skipBackwardCommand = skipBackwardCommand;
}
private string _playPauseIcon = FontAwesome.icon_play;
public string PlayPauseIcon
{
get { return _playPauseIcon; }
set { _playPauseIcon = value; RaisePropertyChanged(() => PlayPauseIcon); }
}
private IPlayCommand _playCommand;
public IPlayCommand PlayCommand
{
get { return _playCommand; }
set { _playCommand = value; RaisePropertyChanged(() => PlayCommand); }
}
private IStopCommand _stopCommand;
public IStopCommand StopCommand
{
get { return _stopCommand; }
set { _stopCommand = value; RaisePropertyChanged(() => StopCommand); }
}
private ISkipForwardCommand _skipForwardCommand;
public ISkipForwardCommand SkipForwardCommand
{
get { return _skipForwardCommand; }
set { _skipForwardCommand = value; RaisePropertyChanged(() => SkipForwardCommand); }
}
private ISkipBackwardCommand _skipBackwardCommand;
public ISkipBackwardCommand SkipBackwardCommand
{
get { return _skipBackwardCommand; }
set { _skipBackwardCommand = value; RaisePropertyChanged(() => SkipBackwardCommand); }
}
}
On our View we've got buttons that bind to those commands, and all is working as expected.
However
Our view also has a SeekBar that we're going to use for quick scrubbing by the user. The seekbar needs to do two things...
allow the user quickly navigate to the spot that's required (this one "should" be easy
automatically update based on the track's current progress (this one I'm stuck on)
How can I write a notifier that triggers every second, and automatically update the seekbar binding? This notifier would need to live in the PCL with the Command Objects so that it can work cross platform. I'm struggling with getting started on this... I'm not sure where to create it or how to wire it up.

automatically update based on the track's current progress (this one I'm stuck on)
You should be able to do this using a binding to a View property. Here's some pseudo code:
In the ViewModel, add the SeekPosition property:
public double SeekPosition { /* normal INPC get/set */ }
In the View:
add a property and event pair like:
public event EventHandler CurrentPositionChanged;
public double CurrentPosition
{
get { return _mediaPlayer.CurrentPosition; }
set
{
_mediaPlayer.SeekTo(value);
}
}
add a timer to fire the CurrentPositionChanged event on the UI thread. For Android, create this timer in OnResume and destroy it in OnPause.
add a binding in OnCreate:
public override void OnCreate(args)
{
// normal base call and inflate
// ...
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(this).For(v => v.CurrentPosition).To(vm => vm.SeekPosition);
set.Apply();
}
Note that this approach doesn't use a timer in the PCL. This is because other platforms like iOS and Windows shouldn't need the timer - as they should be able to use progress callbacks/events from the media players on those platforms instead.

Related

2 classes (1 VM 1 service) changing/accessing a property in a singleton class

So i'm trying to use my "mediator" class as an in-between for my service and my VM when it comes to a couple of booleans.
In my mediator, we have this:
private bool isAddGroupChecked = false;
public bool IsAddGroupChecked
{
get { return isAddGroupChecked; }
set
{
isAddGroupChecked = value;
NotifyPropertyChanged();
}
}
My service retrieves/changes this value by doing the following (for changing)
ActionMediator.Instance.IsAddGroupChecked = false;
My VM can have that boolean changed through the view and has the following property:
public ActionMediator ActionMediator
{
get { return ActionMediator.Instance; }
}
public bool IsAddGroupChecked
{
get { return ActionMediator.IsAddGroupChecked; }
set
{
ActionMediator.IsAddGroupChecked = value;
NotifyOfPropertyChange(() => IsAddGroupChecked);
}
}
Problem is, when you click the toggle (isAddGroupChecked) the value on the singleton changes to True correctly. However, when my service changes the value (say back to false), the VM isn't being notified of that..... where am I going wrong? I'm doing this so my service and VM are not coupled to eachother by this....
It looks like the PropertyChanged event from your singleton has not been assigned on your VM, and that is why it is not being notified, as your service will change the status on the singleton, not on the VM.
So you have (at least) three options
Hanlde the PropertyChanged event from your singleton on your VM to change your VM state.
public VM()
{
ActionMediator.Instance.PropertyChanged += new PropertyChangedEventHandler(Mediator_PropertyChanged);
}
private void Mediator_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsAddGroupChecked")
this.IsAddGroupChecked = ActionMediator.Instance.IsAddGroupChecked;
}
If you go for this one, it is also a good idea to modify you mediator to avoid looping
private bool isAddGroupChecked = false;
public bool IsAddGroupChecked
{
get { return isAddGroupChecked; }
set
{
if (value != isAddGroupChecked)
{
isAddGroupChecked = value;
NotifyPropertyChanged();
}
}
}
Give an instance of your VM to your service so it can change the state directly (not a good idea though)
Remove the IsAddGroupChecked property from your VM and bind your view directly to ActionMediator.IsAddGroupChecked

Updating value in viewmodel from bluetooth characteristic

I use the Xabre BLE library for Xamarin studio. Within my project, I have a bluetooth class, and a ViewModel.
When my app connects to a specified characteristic, I subscribe to a characteristic.ValueUpdated event, from which I update a value in my viewmodel.
In my viewmodel, I have a propertychanged eventhandler, which listens to updates from the bluetooth class.
For some reason however, the setter is not updating my value
Bluetooth class:
public class Bluetooth
{
//code to respectively connect and disconnect
public async void GetValuesFromCharacteristic()
{
CarouselViewModel viewModel = new CarouselViewModel();
Characteristic.ValueUpdated += (s, a) =>
{
viewModel.CurrentValue = Characteristic.Value[7].ToString();
};
await Characteristic.StartUpdatesAsync();
}
}
ViewModel:
public class CarouselViewModel
{
private _currentValue;
public CarouselViewModel()
{
}
public string CurrentValue
{
get
{
return _currentValue;
}
set
{
if(_currentValue != value)
{
_currentValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_currentValue));
}
}
}
}

How can I stop a Device.StartTimer() when I change pages?

I have a ListView that I am updating every 5 seconds using Device.StartTimer() and I would like to stop the timer when it leaves the ViewModel page. as you must intuit necsito do this because Device.StartTimer () is global and even when I change the page is still updating my ListView, how can I make ViewModel know that I'm changing pages?
This is part of my ViewModel:
private ObservableCollection sensors;
public ObservableCollection<PcData> Sensors
{
get { return sensors; }
set
{
sensors = value;
OnPropertyChanged();
}
}
public MonitoringTabsViewModel(string idCode, string description)
{
Description = description;
LoadSensors(idCode);
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
RefreshSensors(idCode);
return true;
});
}
private async void LoadSensors(string idCode)
{
Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
}
private async void RefreshSensors(string idCode)
{
Sensors = null;
Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
}
In the end I have come to the following implementation which actually does what I wanted:
ViewModel:
public class MonitoringTabsViewModel : Notificable
{
public string IdCode { get; set; }
public bool InPage { get; set; }
private string description;
public string Description
{
get { return description; }
set
{
description = value;
OnPropertyChanged();
}
}
private ObservableCollection<PcData> sensors;
public ObservableCollection<PcData> Sensors
{
get { return sensors; }
set
{
sensors = value;
OnPropertyChanged();
}
}
public MonitoringTabsViewModel(string idCode, string description)
{
IdCode = idCode;
Description = description;
LoadSensors(idCode);
MessagingCenter.Subscribe<MonitoringView>(this, "OnAppearing", (sender) =>
{
InPage = true;
});
MessagingCenter.Subscribe<MonitoringView>(this, "OnDisAppearing", (sender) =>
{
InPage = false;
});
Device.StartTimer(TimeSpan.FromSeconds(5), TimerCallBack);
}
private bool TimerCallBack()
{
if (InPage)
{
RefreshSensors(IdCode);
MessagingCenter.Unsubscribe<MonitoringView>(this, "OnAppearing");
return true;
}
else
{
MessagingCenter.Unsubscribe<MonitoringView>(this, "OnDisAppearing");
return false;
}
}
private async void LoadSensors(string idCode)
{
Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
}
private async void RefreshSensors(string idCode)
{
Sensors = null;
Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
}
}
View:
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Send<MonitoringView>(this, "OnAppearing");
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Send<MonitoringView>(this, "OnDisAppearing");
}
There are still two things that concern me:
1. I do not know if the management I'm giving to the MessagingCenter is appropriate, as you can see I'm unsubscribing in my TimerCallBack method, by putting breakpoints in the two calls to the unsubscribe method I see that while the timer is running every 5 seconds The unsubscribe method of the onAppearing message is still called.
2. Although this implmentacion works, I still have the problem that when sleeping the application or put it in the background is still running my method RefreshSensors () and I would like to be in segudno flat also stop the execution.
Could someone give me ideas of these two concerns that I still have?
Page has 2 indicator methods OnAppearing() & OnDisappearing() depends on your setup you should hookup to this events and notify the ViewModel.
This can be done in multiple ways:
Page may have a direct or indirect reference (BindingContext) to the ViewModel so just hookup.
You can use MessagingCenter.
If you have a custom handmade NavigationService you could hookup there.
Use existing MVVM Framework, there are plenty of them and most of them support this scenario
I still have the problem that when sleeping the application or put it
in the background is still running my method RefreshSensors ()
If you look in you App.xaml.cs file, you'll find the following methods:
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
}

MvvmCross UITextField custom binding

So I am trying to implement a custom binding for a UITextField in MvvmCross, pretty much along the lines of Binding 'GO' key on Software Keyboard - i.e. trying to bind a text field to automatically fire an event when the Done button is tapped on the keyboard (so binding to ShouldReturn). I also need to bind the text field's EditingDidBegin and EditingDidEnd events. Because I am binding more than one event, I have created a MvxPropertyInfoTargetBinding as follows:
public class MyTextFieldTargetBinding : MvxPropertyInfoTargetBinding<UITextField>
{
private ICommand _command;
protected UITextField TextField
{
get { return (UITextField)Target; }
}
public MyTextFieldTargetBinding(object target, PropertyInfo targetPropertyInfo) : base(target, targetPropertyInfo)
{
TextField.ShouldReturn += HandleShouldReturn;
TextField.EditingDidBegin += HandleEditingDidBegin;
TextField.EditingDidEnd += HandleEditingDidEnd;
}
private bool HandleShouldReturn(UITextField textField)
{
if (_command == null) {
return false;
}
var text = textField.Text;
if (!_command.CanExecute (text)) {
return false;
}
textField.ResignFirstResponder();
_command.Execute(text);
return true;
}
private void HandleEditingDidBegin (object sender, EventArgs e)
{
// do something
}
private void HandleEditingDidEnd (object sender, EventArgs e)
{
// do something
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override void SetValue(object value)
{
var command = value as ICommand;
_command = command;
}
public override Type TargetType
{
get { return typeof(ICommand); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (TextField != null)
{
TextField.ShouldReturn -= HandleShouldReturn;
TextField.EditingDidBegin -= HandleEditingDidBegin;
TextField.EditingDidEnd -= HandleEditingDidEnd;
}
}
base.Dispose(isDisposing);
}
}
My first question is: am I correct in creating one MvxPropertyInfoTargetBinding for all the events? Relatedly, I don't get the difference between MvxPropertyInfoTargetBinding and MvxTargetBinding. According to MVVMCross Binding decimal to UITextField removes decimal point the former is used when replacing an existing binding, the latter for known properties and event pairs. So am I using the correct one?
Secondly (and the real crux of my problem), my code works except for SetValue - it is fired, but the value is null. Here is what I have in my Setup file:
protected override void FillTargetFactories (IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories (registry);
registry.RegisterPropertyInfoBindingFactory(typeof(MyTextFieldTargetBinding), typeof(UITextField), "Text");
}
I don't do anything in my View - perhaps that is where the issue lies?
EDIT:
My ViewModel:
public class LoginViewModel : MvxViewModel
{
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; RaisePropertyChanged(() => Password); }
}
private MvxCommand _login;
public ICommand Login
{
get {
_login = _login ?? new MvxCommand(DoLogin);
return _login;
}
}
public LoginViewModel(ILoginManager loginManager)
{
_loginManager = loginManager;
}
private void DoLogin()
{
// call the login web service
}
}
In my `View', I don't do anything fancy (I do create the View elements in a XIB):
public override void ViewDidLoad()
{
base.ViewDidLoad ();
this.NavigationController.SetNavigationBarHidden(true, false);
var set = this.CreateBindingSet<LoginView, Core.ViewModels.LoginViewModel>();
set.Bind(usernameTextField).To(vm => vm.Username);
set.Bind(passwordTextField).To(vm => vm.Password);
set.Bind (loginButton).To (vm => vm.Login);
set.Apply();
}
No interesting Trace messages.
1. What is special about PropertyInfoTargetBinding?
The question you reference - MVVMCross Binding decimal to UITextField removes decimal point - gives the key to the difference between MvxTargetBinding and MvxPropertyInfoTargetBinding:
TargetBinding can be used for any arbitrary binding - e.g. for a non-propertyInfo-based binding
PropertyInfoTargetBinding inherits from TargetBinding and can only be used with actual C# Properties - because it uses PropertyInfo via Reflection.
In your case, since you aren't actually using the Text property via Reflection, then I'd be tempted not to use a PropertyInfoTargetBinding and to steer clear of the Text name as well - instead just write a custom TargetBinding.
the former is used when replacing an existing binding
This is definitely not true - instead any binding can be used to replace another binding - as the answer on the other question says
MvvmCross operates a simple 'last registered wins' system
For more on custom bindings, take a look at:
the N=28 video in http://mvvmcross.blogspot.co.uk/
take a look through some of the "standard" bindings that ship with MvvmCross
Droid - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/Target
iOS - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Target
note that most of these use either MvxPropertyInfoTargetBinding or MvxConvertingTargetBinding as a base class
2. Why is my SetValue getting null?
Your current binding code is asking for an ICommand:
public override Type TargetType
{
get { return typeof(ICommand); }
}
But your View code is currently binding the View to a string:
// View
set.Bind(usernameTextField).To(vm => vm.Username);
// ViewModel
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
To solve this...
Work out what you want to bind to - is it an ICommand (e.g. and MvxCommand) or is it a string?
Change the View and the Binding to reflect this.

C#: data binding a single, custom class to form controls (checkbox?)

I'm writing a desktop application in Visual Studio 2008 / C#
I finished (mostly) writing an engine which generates a working schedule for the week for a small company; a form of a course-scheduling problem
Right now I'm designing a form in which the user can determine the initial parameters, or criteria, for the engine to adhere to (as some settings are optional)
I've got a class named EngineParameters, which holds all of those settings.
For the purpose of databinding, I created a bndEngineParameters class, which encapsulates all the relevant fields with getters and setters
public class bndEngineParameters
{
private engineParameters _parameters;
public bndEngineParameters(engineParameters ep)
{
this._parameters = ep;
}
public bool avoidGrouping
{
get { return _parameters.avoidGrouping; }
set { _parameters.avoidGrouping = value; }
}
public bool avoidWeekends
{
get { return _parameters.avoidWeekends; }
set { _parameters.avoidWeekends = value; }
}
public bool keyFlow
{
get { return _parameters.keyFlow; }
set { _parameters.keyFlow = value; }
}
public bool keyFlowAssistants
{
get { return _parameters.keyFlowAssistants; }
set { _parameters.keyFlowAssistants = value; }
}
}
It's not complete - there will be int values (maximum number of hours one can work etc.); I want those bool values to be bound to checkboxes on my form
And it's at that trivial task where I surprisingly ran into problems
Using "Add New DataSource" wizard, I created a binding source
private System.Windows.Forms.BindingSource bndEngineParametersBindingSource;
I then bound the Checked property of my Checkbox to the respective property of my binding source:
I implemented a local variable boundParameters so that I get an access to the parameters set by the user
public partial class formGenerateRota : Form
{
public bndEngineParameters boundParameters;
// (...)
public formGenerateRota()
{
InitializeComponent();
}
private void formGenerateRota_Load(object sender, EventArgs e)
{
boundParameters = new bndEngineParameters(new engineParameters());
bndEngineParametersBindingSource.Add(boundParameters);
}
// (...)
}
And what? Nothing happens. There is an bndEngineParameters object under bndEngineParametersBindingSource.Current (in run-time of course), but the avoidWeekends value never changes (when I check the checkbox on and off), and the bndEngineParametersBindingSource_CurrentItemChanged event is never fired
What's wrong?
SORRY! it does change, but only after the checkbox loses focus (after validation).
I'm stupid sometimes
If I'm doing something wrong anyway (I'm not any good with data binding), I'd very much appreciate if you point it out of course!
Two common issues:
set the DataSourceUpdateMode to OnPropertyChanged
(optional) to receive changes from the object, implement the {name}Changed event pattern or INotifyPropertyChanged
To be honest though, I'm sure most of that isn't necessary; you should just be able to say:
myCheckbox.Bindings.Add("Checked", myEngineParameters, "avoidWeekends",
false, DataSourceUpdateMode.OnPropertyChanged);
Full example:
using System;
using System.Diagnostics;
using System.Windows.Forms;
class EngineParameters {
private bool avoidWeekends;
public bool AvoidWeekends {
get { return avoidWeekends; }
set {
avoidWeekends = value;
Debug.WriteLine("AvoidWeekends => " + value);
}
}
}
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
using(Form form = new Form())
using (CheckBox myCheckbox = new CheckBox()) {
EngineParameters myEngineParameters = new EngineParameters();
myEngineParameters.AvoidWeekends = true;
form.Controls.Add(myCheckbox);
myCheckbox.DataBindings.Add("Checked", myEngineParameters, "AvoidWeekends",
false, DataSourceUpdateMode.OnPropertyChanged);
Application.Run(form);
}
}
}
Instead of this:
bndEngineParametersBindingSource.Add(boundParameters);
do this:
bndEngineParametersBindingSource.DataSource = boundParameters;

Categories