i implement RangeSliderView binding in MvxAndroidTargetBinding, but it not working
this my code:
public class RangeSliderLeftValueBinding : MvxAndroidTargetBinding
{
private readonly RangeSliderView rangeSliderView;
public RangeSliderLeftValueBinding(RangeSliderView rangeSliderView)
: base(rangeSliderView)
{
this.rangeSliderView = rangeSliderView;
}
public override Type TargetType
{
get { return typeof(float); }
}
public override void SubscribeToEvents()
{
rangeSliderView.LeftValueChanged += RangeSliderViewOnLeftValueChanged;
}
private void RangeSliderViewOnLeftValueChanged(float value)
{
FireValueChanged(value);
}
protected override void SetValueImpl(object target, object value)
{
}
}
this code in setup:
registry.RegisterCustomBindingFactory<RangeSliderView>("ProgressLeft", view => new RangeSliderLeftValueBinding(view));
code in file xaml:
local:MvxBind="ProgressLeft abc, Mode=TwoWay"
Please help me !!!
Thanks.
Related
I am using WinUI 3 UWP TabView in my App. I know that WinUI 3 is still in Preview stage for UWP. But still I want to know a workaround for my issue as I want to use TabView in my App. I have gone through the Official Documentation and GitHub Samples but I couldn't find a solution. The TabView is NOT displaying a New Tab whenever a New Document is added to the Collection. I have searched a lot but couldn't find a solution. Kindly, share a solution/workaround. You might suggest using WinUI 2 since it is stable for UWP. But, I have already tried that and WinUI 2 controls are not blending well with existing UWP Controls. But WinUI 3 blends perfectly. All other controls except TabView are working well. When I switch from DataBinding to Manually maintaining a list of TabItems, it works perfectly. But, I don't want Boilerplate code. I want to achieve the same with DataBinding. I am new to MVVM. So, if there's a problem with my ViewModel, do share a workaround.
This is my ViewModel Class:
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
using System.IO;
using System.Text;
using MyApp.Utilities;
using System.Runtime.CompilerServices;
namespace MyApp.ViewModels
{
public class TextDocument : INotifyPropertyChanged
{
private int _documentId;
private string _fileName;
private string _filePath;
private string _textContent;
private Encoding _encoding;
private LineEnding _lineEnding;
private bool _isSaved;
public int DocumentId
{
get
{
return _documentId;
}
set
{
_documentId = value;
OnPropertyChanged(ref _documentId, value);
}
}
public string FileName
{
get
{
return _fileName;
}
set
{
OnPropertyChanged(ref _fileName, value);
}
}
public string FilePath
{
get
{
return _filePath;
}
set
{
OnPropertyChanged(ref _filePath, value);
FileName = Path.GetFileName(_filePath);
}
}
public string TextContent
{
get
{
return _textContent;
}
set
{
OnPropertyChanged(ref _textContent, value);
}
}
public Encoding FileEncoding
{
get
{
return _encoding;
}
set
{
OnPropertyChanged(ref _encoding, value);
}
}
public LineEnding LineEnding
{
get
{
return _lineEnding;
}
set
{
OnPropertyChanged(ref _lineEnding, value);
}
}
public string TabHeader
{
get
{
return string.IsNullOrEmpty(FileName) ? "Untitled Document" : FileName;
}
}
public bool IsSaved
{
get
{
return _isSaved;
}
set
{
OnPropertyChanged(ref _isSaved, value);
}
}
public bool IsInvalidFile
{
get
{
return (string.IsNullOrEmpty(FilePath) || string.IsNullOrEmpty(FileName));
}
}
public override bool Equals(object obj)
{
if (ReferenceEquals(obj, null))
return false;
if (ReferenceEquals(this, obj))
return true;
var comp = (TextDocument)obj;
return this.DocumentId == comp.DocumentId;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged<T>(ref T property, T value, [CallerMemberName] string propertyName = "")
{
property = value;
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And this is my XAML Code for TabView:
<TabView x:Name="MyTabView" AddTabButtonClick="TabView_AddTabButtonClick" TabCloseRequested="TabView_TabCloseRequested"
SelectionChanged="TabView_SelectionChanged"
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Background="White"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
TabItemsChanged="TabView_TabItemsChanged"
SelectedIndex="0"
TabItemsSource="{x:Bind MyDocuments,Mode=OneWay}"
>
<TabView.TabItemTemplate>
<DataTemplate x:DataType="viewmodels:TextDocument">
<TabViewItem Header="{x:Bind TabHeader,Mode=OneWay}" IconSource="{x:Null}">
<TabViewItem.Content>
<TextBox x:Name="TextBoxInsideTab" Grid.Column="0" Grid.Row="0"
PlaceholderText="Drag and drop a file here or start typing"
Text="{x:Bind TextContent,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="24"
UseSystemFocusVisuals="False"
BorderThickness="0"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
TextWrapping="Wrap"
IsSpellCheckEnabled="False"
CanBeScrollAnchor="True"
TextChanged="TextBox_TextChanged"
AcceptsReturn="True"
IsTabStop="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
/>
</TabViewItem.Content>
</TabViewItem>
</DataTemplate>
</TabView.TabItemTemplate>
</TabView>
And this is my C# code:
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using MyApp.ViewModels;
using Windows.Storage.Pickers;
using Windows.Storage;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MyApp
{
public sealed partial class MainPage : Page
{
private ObservableCollection<TextDocument> MyDocuments;
public MainPage()
{
this.InitializeComponent();
MyDocuments = new ObservableCollection<TextDocument>()
{
new TextDocument()
};
}
private void TabView_AddTabButtonClick(TabView sender, object args)
{
AddTabViewItemDefault(sender.TabItems.Count);
}
private void AddTabViewItemDefault(int index)
{
MyDocuments.Add(new TextDocument());
}
private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
MyDocuments.Remove(args.Item as TextDocument);
}
private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
}
ObservableCollection<T> and INotifyCollectionChanged currently don't work in UWP apps.
You need to implement your own custom collection as a workaround:
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Interop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using NotifyCollectionChangedAction = Microsoft.UI.Xaml.Interop.NotifyCollectionChangedAction;
public class CustomObservableCollection<T> : Collection<T>, Microsoft.UI.Xaml.Interop.INotifyCollectionChanged, INotifyPropertyChanged
{
private ReentrancyGuard reentrancyGuard = null;
private class ReentrancyGuard : IDisposable
{
private CustomObservableCollection<T> owningCollection;
public ReentrancyGuard(CustomObservableCollection<T> owningCollection)
{
owningCollection.CheckReentrancy();
owningCollection.reentrancyGuard = this;
this.owningCollection = owningCollection;
}
public void Dispose()
{
owningCollection.reentrancyGuard = null;
}
}
public CustomObservableCollection() : base() { }
public CustomObservableCollection(IList<T> list) : base(list.ToList()) { }
public CustomObservableCollection(IEnumerable<T> collection) : base(collection.ToList()) { }
public event Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventHandler CollectionChanged;
public void Move(int oldIndex, int newIndex)
{
MoveItem(oldIndex, newIndex);
}
protected IDisposable BlockReentrancy()
{
return new ReentrancyGuard(this);
}
protected void CheckReentrancy()
{
if (reentrancyGuard != null)
{
throw new InvalidOperationException("Collection cannot be modified in a collection changed handler.");
}
}
protected override void ClearItems()
{
CheckReentrancy();
TestBindableVector<T> oldItems = new TestBindableVector<T>(this);
base.ClearItems();
OnCollectionChanged(
NotifyCollectionChangedAction.Reset,
null, oldItems, 0, 0);
}
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
TestBindableVector<T> newItem = new TestBindableVector<T>();
newItem.Add(item);
base.InsertItem(index, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Add,
newItem, null, index, 0);
}
protected virtual void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[oldIndex]);
TestBindableVector<T> newItem = new TestBindableVector<T>(oldItem);
T item = this[oldIndex];
base.RemoveAt(oldIndex);
base.InsertItem(newIndex, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Move,
newItem, oldItem, newIndex, oldIndex);
}
protected override void RemoveItem(int index)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[index]);
base.RemoveItem(index);
OnCollectionChanged(
NotifyCollectionChangedAction.Remove,
null, oldItem, 0, index);
}
protected override void SetItem(int index, T item)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[index]);
TestBindableVector<T> newItem = new TestBindableVector<T>();
newItem.Add(item);
base.SetItem(index, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Replace,
newItem, oldItem, index, index);
}
protected virtual void OnCollectionChanged(
NotifyCollectionChangedAction action,
IBindableVector newItems,
IBindableVector oldItems,
int newIndex,
int oldIndex)
{
OnCollectionChanged(new Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs(action, newItems, oldItems, newIndex, oldIndex));
}
protected virtual void OnCollectionChanged(Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
CollectionChanged?.Invoke(this, e);
}
}
#pragma warning disable 0067 // PropertyChanged is never used, raising a warning, but it's needed to implement INotifyPropertyChanged.
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore 0067
}
public class TestBindableVector<T> : IList<T>, IBindableVector
{
IList<T> implementation;
public TestBindableVector() { implementation = new List<T>(); }
public TestBindableVector(IList<T> list) { implementation = new List<T>(list); }
public T this[int index] { get => implementation[index]; set => implementation[index] = value; }
public int Count => implementation.Count;
public virtual bool IsReadOnly => implementation.IsReadOnly;
public void Add(T item)
{
implementation.Add(item);
}
public void Clear()
{
implementation.Clear();
}
public bool Contains(T item)
{
return implementation.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
implementation.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
return implementation.GetEnumerator();
}
public int IndexOf(T item)
{
return implementation.IndexOf(item);
}
public void Insert(int index, T item)
{
implementation.Insert(index, item);
}
public bool Remove(T item)
{
return implementation.Remove(item);
}
public void RemoveAt(int index)
{
implementation.RemoveAt(index);
}
IEnumerator IEnumerable.GetEnumerator()
{
return implementation.GetEnumerator();
}
public object GetAt(uint index)
{
return implementation[(int)index];
}
public IBindableVectorView GetView()
{
return new TestBindableVectorView<T>(implementation);
}
public bool IndexOf(object value, out uint index)
{
int indexOf = implementation.IndexOf((T)value);
if (indexOf >= 0)
{
index = (uint)indexOf;
return true;
}
else
{
index = 0;
return false;
}
}
public void SetAt(uint index, object value)
{
implementation[(int)index] = (T)value;
}
public void InsertAt(uint index, object value)
{
implementation.Insert((int)index, (T)value);
}
public void RemoveAt(uint index)
{
implementation.RemoveAt((int)index);
}
public void Append(object value)
{
implementation.Add((T)value);
}
public void RemoveAtEnd()
{
implementation.RemoveAt(implementation.Count - 1);
}
public uint Size => (uint)implementation.Count;
public IBindableIterator First()
{
return new TestBindableIterator<T>(implementation);
}
}
public class TestBindableVectorView<T> : TestBindableVector<T>, IBindableVectorView
{
public TestBindableVectorView(IList<T> list) : base(list) { }
public override bool IsReadOnly => true;
}
public class TestBindableIterator<T> : IBindableIterator
{
private readonly IEnumerator<T> enumerator;
public TestBindableIterator(IEnumerable<T> enumerable) { enumerator = enumerable.GetEnumerator(); }
public bool MoveNext()
{
return enumerator.MoveNext();
}
public object Current => enumerator.Current;
public bool HasCurrent => enumerator.Current != null;
}
Page:
public sealed partial class MainPage : Page
{
private CustomObservableCollection<TextDocument> MyDocuments;
public MainPage()
{
this.InitializeComponent();
MyDocuments = new CustomObservableCollection<TextDocument>()
{
new TextDocument()
};
}
...
}
I think your code in the constructor might be breaking the initial binding to the ObservableCollection. Try this code:
private ObservableCollection<TextDocument> MyDocuments {get;} = new ObservableCollection<TextDocument>();
public MainPage()
{
this.InitializeComponent();
MyDocuments.Add(new TextDocument());
}
Does it help?
My TabbedPage uses a Binding Property, which is defined in the tabbed page's ViewModel, for showing a Badge text.
I am setting the badge property when initializing the view (actually when it (re)appears). However, sometimes the badge text is changing from outside of my ViewModel(s), this is because I have a SignalR method which is called when a new message is being added by another application.
Though, when this happens the OnAppearing method of my tabbed viewmodel is obviously not called. So the question is, how can I 'notify' the tabbedpage viewmodel that the badge text should be changed.
I think the (best) way to do this is using somekind of Event. Since all of my ViewModels inherit from a 'ViewModelBase' I could implement the event notification / change in the ViewModelBase and override the property in my TabbedPage ViewModel.
Though, sadly my knowledge about using Events / EventArgs is limited and the stuff I found about it is not working.
Is using EventArgs the best way to solve this problem? And if so, could anyone give any pointers how to implement it properly.
*On a side-note, I am also using Prism
My TabbedPage ViewModel:
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
// (omitted) Logic for setting the MessageCount property
}
}
ViewModelVase:
public class ViewModelBase : BindableBase, IInitialize, IInitializeAsync, INavigationAware, IDestructible, IActiveAware
{
public event EventHandler MessageAddedEventArgs; // this should be used to trigger the MessageCount change..
protected INavigationService NavigationService { get; private set; }
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
IsNotConnected = Connectivity.NetworkAccess != NetworkAccess.Internet;
}
private bool _isNotConnected;
public bool IsNotConnected
{
get { return _isNotConnected; }
set { SetProperty(ref _isNotConnected, value); }
}
~ViewModelBase()
{
Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
}
async void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
IsNotConnected = e.NetworkAccess != NetworkAccess.Internet;
if (IsNotConnected == false)
{
await DataHubService.Connect();
}
}
public virtual void Initialize(INavigationParameters parameters)
{
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
public virtual Task InitializeAsync(INavigationParameters parameters)
{
return Task.CompletedTask;
}
}
SignalR Datahub which should trigger the event:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
// event that message count has changed should be triggered here..
}
});
}
catch (Exception ex)
{
// ...
}
}
}
As pointed out by #Jason, this specific problem is a good use case for using the MessagingCenter.
In the end the implementation looks as following:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
MessagingCenter.Send("UpdateMessageCount", "Update");
}
});
}
catch (Exception ex)
{
// ...
}
}
}
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
MessagingCenter.Subscribe<string>("UpdateMessageCount", "Update", async (a) =>
{
await UpdateMessageCount();
});
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
UpdateMessageCount();
}
async Task UpdateMessageCount()
{
int messageCount = await App.Database.GetNewMessageCountAsync();
MessageCount = messageCount.ToString();
}
}
I am trying to override the RemoteControlRecieved method of the UIApplication and getting the following error:
NSInternalInconsistencyException Reason: There can only be one UIApplication instance.
I understand the issue but have not idea how to solve it. I have a StreamingService class which implements the UIApplication and IStreaming interface. All of my AVPlayer functionality is in this class. the StreamingViewModel class calls the
DependencyService.Get<IStreaming>().Play().
When this line is called I get the above error. I am not sure how to override RemoteControlRecieved from either the StreamingService or the StreamingViewModel class.
any help with code samples is greatly appreciated.
Classes below
public class StreamingViewModel : INotifyPropertyChanged
{
public bool DisplayPlay { get => !isPlaying; }
public bool DisplayPauseStop { get => isPlaying; }
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
bool isPlaying;
bool IsPlaying
{
get => isPlaying;
set
{
isPlaying = value;
// Notify the property has changed
OnPropertyChanged("DisplayPlay");
OnPropertyChanged("DisplayPauseStop");
}
}
public void Play()
{
DependencyService.Get<IStreaming>().Play();
IsPlaying = true;
}
public void Pause()
{
DependencyService.Get<IStreaming>().Pause();
IsPlaying = false;
}
public void Stop()
{
DependencyService.Get<IStreaming>().Stop();
IsPlaying = false;
}
}
Class StreamingService
[assembly: Xamarin.Forms.ExportRenderer(typeof(MainPage), typeof(StreamingService))]
[assembly: Dependency(typeof(StreamingService))]
namespace test.iOS
{
public class StreamingService : PageRenderer, IStreaming
{
AVPlayer player;
bool isPrepared;
string dataSource = "https://stream.voxx.pro/radio/8260/radio.mp3";
public override void ViewDidLoad()
{
Console.WriteLine("StreamService ViewDidLoad");
base.ViewDidLoad();
}
public StreamingService()
{
Console.WriteLine("StreamService Default Constructor");
}
public void Play()
{
Console.WriteLine("Play");
if (!isPrepared || player == null)
player = AVPlayer.FromUrl(NSUrl.FromString(dataSource));
//Audio player Notification in lock screen
MPNowPlayingInfo nowPlayingInfo;
nowPlayingInfo = new MPNowPlayingInfo();
nowPlayingInfo.Artist = "Radio Caravan";
nowPlayingInfo.Title = "Join The Caravan";
// Register for receiving controls from lock screen and controlscreen
MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = nowPlayingInfo;
//var command = MPRemoteCommandCenter.Shared;
//command.PlayCommand.Enabled = true;
//command.PauseCommand.Enabled = true;
//command.NextTrackCommand.Enabled = false;
//command.PreviousTrackCommand.Enabled = false;
isPrepared = true;
player.Play();
base.BecomeFirstResponder();
//To listen changes in lock screen
UIApplication.SharedApplication.BeginReceivingRemoteControlEvents();
}
public void Pause()
{
player.Pause();
}
public void Stop()
{
player.Dispose();
isPrepared = false;
}
public override void RemoteControlReceived(UIEvent theEvent)
{
Console.WriteLine("Remote Control Received");
base.RemoteControlReceived(theEvent);
if (theEvent.Subtype == UIEventSubtype.RemoteControlPause)
{
Console.WriteLine("Remote Pause");
player.Pause();
}
else if (theEvent.Subtype == UIEventSubtype.RemoteControlPlay)
{
Console.WriteLine("Remote Play");
player.Play();
}
}
}
}
MainPage.cs
namespace test
{
public partial class MainPage : ContentPage
{
private StreamingViewModel ViewModel { get { return (StreamingViewModel)this.BindingContext; } }
public MainPage()
{
InitializeComponent();
On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea(true);
BindingContext = new StreamingViewModel();
}
// Callbacks to images tapped
private void Play_tapped(object sender, EventArgs e)
{
ViewModel.Play();
}
private void Pause_tapped(object sender, EventArgs e)
{
ViewModel.Pause();
}
private void Stop_tapped(object sender, EventArgs e)
{
ViewModel.Stop();
}
}
}
Interface: IStreaming
public interface IStreaming
{
void Play();
void Pause();
void Stop();
}
NSInternalInconsistencyException Reason: There can only be one UIApplication instance.
You could custom PageRenderer to override RemoteControlReceived method to check whether it works. Because this will use the existing UIApplication.
About isPlaying property, you could use Bindable Properties to define in CustomPage.
About Play/Pause/Stop method, you could use MessagingCenter to send Message from Forms to CustomPageRenderer.
For example, part code of CustomPage as follows:
public partial class CustomPage: ContentPage
{
public CustomPage()
{
InitializeComponent();
}
public static readonly BindableProperty IsPlayingProperty = BindableProperty.Create("IsPlaying", typeof(bool), typeof(CustomPage), null);
public bool IsPlaying
{
get { return (bool)GetValue(IsPlayingProperty); }
set { SetValue(IsPlayingProperty, value); }
}
public void Play()
{
MessagingCenter.Send<object, string>(this, "PlayControl", "Play");
IsPlaying = true;
}
public void Pause()
{
MessagingCenter.Send<object, string>(this, "PlayControl", "Pause");
IsPlaying = false;
}
public void Stop()
{
MessagingCenter.Send<object, string>(this, "PlayControl", "Stop");
IsPlaying = false;
}
}
The part code of CustomPageRenderer as follows:
[assembly: Xamarin.Forms.ExportRenderer(typeof(CustomPage), typeof(CustomPageRenderer))]
namespace XamarinForms20.iOS
{
public class CustomPageRenderer : PageRenderer
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
//...
MessagingCenter.Subscribe<object, string>(this, "PlayControl", (sender, arg) =>
{
if(arg == "Play")
{
//...
}else if (arg == "Pause")
{
//...
}else if (arg == "Stop")
{
//...
}
});
}
public override void RemoteControlReceived(UIEvent theEvent)
{
base.RemoteControlReceived(theEvent);
if (theEvent.Subtype == UIEventSubtype.RemoteControlPlay)
player.Play();
else if (theEvent.Subtype == UIEventSubtype.RemoteControlPause)
player.Pause();
}
}
}
===================================Update================================
You could have a try with adding NSNotificationCenter in ViewDidload method for AVAudioSession.InterruptionNotification.
public override void ViewDidLoad()
...
NSObject _notificationHandle = NSNotificationCenter.DefaultCenter.AddObserver(AVAudioSession.InterruptionNotification, HandleNotification);
...
private void HandleNotification(NSNotification notification)
{
// Do something
NSDictionary userinfo = notification.UserInfo;
NSNumber number = (NSNumber)userinfo.ValueForKey(new NSString("AVAudioSessionInterruptionTypeKey"));
int value = (int)number.NIntValue;
if(value == 1)
{
// pause
}else if(value == 0)
{
// countinue to play
AVAudioSession.SharedInstance().SetActive(true);
}
}
I got sort of a typical music player window, music plays and seekbar point is moving while it plays.
I've done it using default mvvmcross binding to the property (which is changed through the EventHandler binding) like here:
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer"
/>
So now I want the user to be able to move it forward and back.
I've been trying to bind it like this:
public class PlayWindowView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var set = this.CreateBindingSet<PlayWindowView, PlayWindowViewModel>();
SeekBar seek = FindViewById<SeekBar>(Resource.Id.playprogress);
set.Bind(seek).For("Max").To(viewModel => viewModel.MaxTimerValue);
set.Bind(seek).For("StopTrackingTouch").To(viewModel => viewModel.PlayProgressChanged);
set.Apply();
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.playwindow_view);
}
}
Viewmodel part looks like this:
public class PlayWindowViewModel : MvxViewModel<ListMenuItemDto>
{
private long _valueOfTimer;
public long ValueOfTimer
{
get { return _valueOfTimer; }
set
{
_valueOfTimer = value;
RaisePropertyChanged(() => ValueOfTimer);
}
}
//...
public MvxAsyncCommand<long> PlayProgressChanged
{
get { return new MvxAsyncCommand<long>(OnPlayProgressChange);}
}
private async Task OnPlayProgressChange(long progr)
{
await _playingService.SetTime((int) progr).ConfigureAwait(false);
}
}
But looks like it's not working.
I mean, it's not even getting into OnPlayProgressChange. But on view is appearing it goes into command PlayProgressChanged one time.
How can I bind this event (and such kind of events like StartTrackingTouch, StopTrackingTouch) to the function correctly?
P.S.
just FYI I using MvvmCross 5
UPD 28.11.2017
Tried custom binding and even Progress binding stoped working now.
So, xaml looks like this now:
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer, OnStopTrackingTouch PlayProgressChanged"
/>
And binder code is here
public class SeekbarStopTrackingTouchEventBinding: MvxAndroidTargetBinding
{
private readonly SeekBar _seekbar;
private IMvxAsyncCommand _command;
public SeekbarStopTrackingTouchEventBinding(SeekBar seekbar) : base(seekbar)
{
_seekbar = seekbar;
_seekbar.StopTrackingTouch += ViewOnStopTrackingTouch;
}
private void ViewOnStopTrackingTouch(object sender, SeekBar.StopTrackingTouchEventArgs e)
{
if (_command != null)
{
_command.Execute(e);
}
}
public override Type TargetType
{
get { return typeof (IMvxAsyncCommand); }
}
protected override void SetValueImpl(object target, object value)
{
_command = (IMvxAsyncCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_seekbar.StopTrackingTouch -= ViewOnStopTrackingTouch;
}
base.Dispose(isDisposing);
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
}
In Setup:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<SeekBar>("OnStopTrackingTouch", (seekbar) => new SeekbarStopTrackingTouchEventBinding(seekbar)));
}
UPD2
Changed binding like this: local:MvxBind="Progress ValueOfTimer; OnStopTrackingTouch PlayProgressChanged" (notice ; here) and event fires now, yes!
But now the thing is - in binder _command is stays null even after SetValueImpl fired and _command = (IMvxAsyncCommand)value; is fine, value points to setted ViewModel property.
How come?
UPD3
Found out it can't cast object to IMvxAsyncCommand.
I fixed that by changing
IMvxAsyncCommand _command to IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> _command;
Will sum everything up in the answer.
But now I got the question - whats best practice in this case?
So, I don't know if it's a good way, but for now its working and I'm kinda happy with it.
Hope this would help somebody like me.
Custom Bindings approach is the key here. Really useful stuff is here:
In MvvmCross how do I do custom bind properties
MvvmCross Custom Event Binding Event Args
MVVMCross Bindings in Android
So, in my case, to make app listen to SeekBar OnStopTrackingTouch event I done this:
Created binding class:
public class SeekbarStopTrackingTouchEventBinding: MvxAndroidTargetBinding
{
private readonly SeekBar _seekbar;
private IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> _command;
private string testString;
public SeekbarStopTrackingTouchEventBinding(SeekBar seekbar) : base(seekbar)
{
_seekbar = seekbar;
_seekbar.StopTrackingTouch += ViewOnStopTrackingTouch;
}
private void ViewOnStopTrackingTouch(object sender, SeekBar.StopTrackingTouchEventArgs e)
{
if (_command != null)
{
_command.Execute(e);
}
}
public override Type TargetType
{
get { return typeof (IMvxAsyncCommand); }
}
protected override void SetValueImpl(object target, object value)
{
try
{
_command = (IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs>)value;
}
catch (Exception e)
{
Log.Error("SOME BINDER FAIL\n\t" + e.Message + "\n", "SOME BINDER FAIL\n\t" + e.Message + "\n");
throw;
}
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_seekbar.StopTrackingTouch -= ViewOnStopTrackingTouch;
}
base.Dispose(isDisposing);
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
In Setup.cs placed this code:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<SeekBar>("OnStopTrackingTouch", (seekbar) => new SeekbarStopTrackingTouchEventBinding(seekbar)));
}
Prepared property in my ViewModel and command executing function:
public IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> PlayProgressChanged
{
get
{
return new MvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs>(OnPlayProgressChange);
}
}
private async Task OnPlayProgressChange(SeekBar.StopTrackingTouchEventArgs e)
{
var progr = e.SeekBar.Progress;
await _playingService.SetTime((int) progr).ConfigureAwait(false);
}
In view layout, inside local:MvxBind linked my ViewModel command with evend name, provided in registry.RegisterFactory in Setup.cs
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer; OnStopTrackingTouch PlayProgressChanged"
/>
i raised questions in google group , they told me to Implement 'CefStringVisitor' class,i did so.
namespace Xilium.CefGlue.WPF.Customer
{
class MyCefStringVisitor : CefStringVisitor
{
private string html;
protected override void Visit(string value)
{
html = value;
}
public string Html
{
get { return html; }
set { html = value; }
}
}
}
But i receive the Text word , instead of HTML soucrce.
How can i get HTML source?
Try this
private sealed class SourceVisitor : CefStringVisitor
{
private readonly Action<string> _callback;
public SourceVisitor(Action<string> callback)
{
_callback = callback;
}
protected override void Visit(string value)
{
_callback(value);
}
}
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
if (frame.IsMain)
{
string HtmlSourceCode;
var visitor = new SourceVisitor(text =>
{
BeginInvoke(new Action(() =>
{
HtmlSourceCode = text;
}));
});
frame.GetSource(visitor);
}
}