I'm currently working with a CustomObject that needs a CustomObjectRenderer for each platform.
I would like to pass a method as parameter to this object, from the XAML side, so I would be able to use this callback, from my renderer.
<control:CustomObject Callback="CallbackFunction"/>
The CallbackFunction(object param) is then declared in the MainPage.xaml.cs of the PCL part.
public partial class MainPage : ContentPage
{
public MainPage()
{
base.BindingContext = this;
}
public void CallbackFunction(object param)
{
Debug.WriteLine((object as Element).Name);
}
}
So, if I'm understanding well, my CustomObject have to be like that:
public CustomObject : Object
{
public Action<object> Callback { get; set; }
}
But I have an error about XAML parsing.. I don't get why this error is thrown..
At the end, what I want to do, it's to call this method from the renderer, and then handle things, do actions from the MainPage.xaml.cs, from the PCL part.
public class CustomObjectRenderer : ObjectRenderer
{
NativeObject nativeObject;
CustomObject customObject;
protected override void OnElementChanged(ElementChangedEventArgs<CustomObject> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customObject = e.NewElement as CustomObject;
nativeObject = Control as NativeObject;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
// Etc etc ....
private void METHOD_CALLED_BY_EVENT(object o)
{
// This method get call by the renderer event and then, I want to call
// the method CallbackFunction(object); and do actions.
customObject.Callback(o as OBJECT_PARAM);
}
}
Ok, it's a bit hard for me to explain my problem to you, so if you don't understand something, let me know.
You can achieve this by using events.
MyView
public class MyView : View
{
public event EventHandler<string> MyEvent;
public void RaiseEvent(string parameter)
{
MyEvent?.Invoke(this, parameter);
}
}
Page.xaml
<local:MyView MyEvent="MyView_OnMyEvent"></local:MyView>
Page.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void MyView_OnMyEvent(object sender, string e)
{
Debug.WriteLine(e);
}
}
Renderer
public class MyViewRenderer : ViewRenderer<MyView, SomeNativeView>
{
private void METHOD_CALLED_BY_EVENT(string param)
{
Element.RaiseEvent(param);
}
}
After lot of tried, which didn't work, I had an idea, I tried and it works as I wanted by asking my question.
First, create your custom object !
CustomView
public class CustomView : View
{
public static readonly BindableProperty MainPageCallbackProperty =
BindableProperty.Create(nameof(MainPageCallback), typeof(Action<object>), typeof(CustomMap), null);
public Action<object> MainPageCallback
{
get { return (Action<object>)GetValue(MainPageCallbackProperty); }
set { SetValue(MainPageCallbackProperty, value); }
}
}
We so use Action which is a container for a method/callback. But in my example, we will use Action<object>. Why? Because it will allows us to have an object has paramter to our callback, so we will be able to bring data back from the renderer.
Then, create a page called MainPage.xaml by example. In the XAML part of this new page, add the following code:
<?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:control="clr-namespace:Project.CustomControl;assembly=Project"
x:Class="Project.Page.MainPage">
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
About this XAML, two parts interest us.
XAML 'References'
xmlns:control="clr-namespace:Project.CustomControl;assembly=Project"
By these this xmlns, you can access your custom control.
Content of the page
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
Now, we bind the MainPageCallback of our object to the MainPageCallbackAction, declared in the C# side.
After that, our MainPage.xaml.cs would seems like that:
public partial class MainPage : ContentPage
{
public Action<object> MainPageCallbackAction { get; set; }
public MainPage()
{
base.BindingContext = this;
MainPageCallbackAction = MainPageCallbackMethod;
InitializeComponent();
}
private void MainPageCallbackMethod(object param)
{
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("Welcome to the Callback :)");
Debug.WriteLine("Emixam23 - Example");
});
}
}
Now, the last thing to look at is the CustomViewRenderer !
public class CustomViewRenderer : ViewRenderer<CustomView, NativeView>
{
CustomView customView;
NativeView nativeView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customView = e.NewElement as CustomView;
nativeView = Control as NativeView;
NativeView.CLicked += METHOD_CALLED_BY_EVENT;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
private void METHOD_CALLED_BY_EVENT(object sender, EventArgs ea)
{
customView.MainPageCallback(ea.something.information);
}
}
And then, take a look at the output, you'll be able to see the following:
Welcome to the Callback :)
Emixam23 - Example
I hope this answer is clear and helps you !
Related
I'm trying to implement a lifecycle effect in Xamarin.Forms, but am having trouble for the iOS version. For some reason, I can't seem to observe the window changing notification event. Below is my code:
public class CustomLifeCycleEffectRouter : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.New;
UIView? _nativeView;
CustomLifeCycleEffect? _lifeCycleEffect;
IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_lifeCycleEffect = Element.Effects.OfType<CustomLifeCycleEffect>().FirstOrDefault() ?? throw new ArgumentNullException($"The effect {nameof(CustomLifeCycleEffect)} can't be null.");
_nativeView = Control ?? Container;
_isLoadedObserverDisposable = _nativeView?.AddObserver("window", ObservingOptions, isWindowAttachedObserver);
}
protected override void OnDetached()
{
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
_isLoadedObserverDisposable.Dispose();
}
private void isWindowAttachedObserver(NSObservedChange nsObservedChange)
{
if (_nativeView.Window != null)
_lifeCycleEffect?.RaiseLoadedEvent(Element);
else
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
}
}
I am well aware that the Xamarin.Community Toolkit has a similar effect, but it fires the event to early; I need it to fire when I can navigate up the hiearchy to the root parent. Can anybody see a problem?
Edit
I've created a small sample to replicate my behaviours and issues. It can be viewed here:
https://github.com/sonic1015/LifeCycleEffectTesting
The goal is to only have the following messages in the debug output:
$"{elementName} is already a page."
$"{elementName} is a child of {pageName}."
and NOT these ones:
$"{elementName} does not have a parent ???."
$"How can {elementName} be loaded and not have a parent in hierarchy ???."
$"WTF??? we never loaded {elementName}."
These messages can be found in the "ViewExtensions" class, and I've the goal is to have every user-created view fire off good messages.
One thing I've noticed:
I also included a variant of the Xamarin Community Toolkit version of the router effect in the platform project, and it actually works, with the exception that it seems if any views are templated, it will fire "loaded" when it does not yet have a parent. I think this is why it originally didn't work for me, so if I could figure out a way to work that little edge case out, I could use that version of the routing effect.
1.Create a ViewLifecycleEffect class that implements RoutingEffect in the shared project like below:
public class ViewLifecycleEffect : RoutingEffect
{
public const string EffectGroupName = "XFLifecycle";
public const string EffectName = "LifecycleEffect";
public event EventHandler<EventArgs> Loaded;
public event EventHandler<EventArgs> Unloaded;
public ViewLifecycleEffect() : base($"{EffectGroupName}.{EffectName}") { }
public void RaiseLoaded(Element element) => Loaded?.Invoke(element, EventArgs.Empty);
public void RaiseUnloaded(Element element) => Unloaded?.Invoke(element, EventArgs.Empty);
}
2.In Mainpage.xmal:
<StackLayout x:Name="MainContainer" Margin="20" VerticalOptions="Center" HorizontalOptions="Center">
<Button Text="CLICK TO REMOVE" Clicked="Button_OnClicked" HorizontalOptions="Center" VerticalOptions="Center">
<Button.Effects>
<effects:ViewLifecycleEffect Loaded="ViewLifecycleEffect_OnLoaded" Unloaded="ViewLifecycleEffect_OnUnloaded"/>
</Button.Effects>
</Button>
</StackLayout>
Code-behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_OnClicked(object sender, EventArgs e)
{
MainContainer.Children.Clear();
}
private void ViewLifecycleEffect_OnLoaded(object sender, EventArgs e)
{
DisplayAlert("LOADED", "Button was added", "OK");
}
private void ViewLifecycleEffect_OnUnloaded(object sender, EventArgs e)
{
DisplayAlert("UNLOADED", "Button was removed", "OK");
}
}
3.Create a IosLifecycleEffect in the iOS project.
[assembly:ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly:ExportEffect(typeof(IosLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.iOS.Effects
{
public class IosLifecycleEffect : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.OldNew | NSKeyValueObservingOptions.Prior;
private ViewLifecycleEffect _viewLifecycleEffect;
private IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
UIView nativeView = Control ?? Container;
_isLoadedObserverDisposable = nativeView?.AddObserver("superview", ObservingOptions, IsViewLoadedObserver);
}
protected override void OnDetached()
{
_viewLifecycleEffect.RaiseUnloaded(Element);
_isLoadedObserverDisposable.Dispose();
}
private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
{
if (!nsObservedChange.NewValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseLoaded(Element);
else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseUnloaded(Element);
}
}
}
EDIT: I have updated this with the two methods recommended
I am writing a simple custom PI (OSISoft) data viewer. I have two classes, one for the UI and one for the PI server interactions/program logic. The property for the data to be displayed has an event that fires when the property is changed. How do I get that change to propagate over to the UI class so the associated text box will automatically refresh?
Original code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue(object sender, TextChangedEventArgs e) {
// ??? something happens here?
}
}
public class ProgLogic {
private int someValue;
public event System.EventHandler SomeValueChanged;
protected void OnSomeValueChanged()
{
SomeValueChanged?.Invoke(this, EventHandlerArgs e);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged();
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 1: Events
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += Logic_SomeValueChanged;
InitializeValues();
}
private void Logic_SomeValueChanged(int obj) {
TextBoxSomeValue.Text = obj.toString();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
logic.SomeValueChanged -= Logic_SomeValueChanged;
}
}
public class ProgLogic {
private int someValue;
public event Action<int> SomeValueChanged;
public virtual void OnSomeValueChanged(int newValue) {
SomeValueChanged?.Invoke(newValue);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged(value);
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 2: MVVM pattern
MainWindow.xaml:
<Window
Closing="Window_Closing"
Title="My App">
<TextBox x:name="TextBoxSomeValue" text="{binding SomeValue, UpdateSourceTrigger=PropertyChanged}" />
</Window>
The important part here is the binding parameter in the text field of the TextBox definition, which points to the PropertyChangedEventHandler.
C# code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
// run some other code when the text box updates
}
}
public class ProgLogic : INotifyPropertyChanged {
private int someValue;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnPropertyChange("SomeValue")
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
ProgLogic now implements INotifyPropertyChanged, which notifies the View of property changes, so that Bindings are updated.
I see you are heading the right way with C# event system. One thing I would change is event type from System.EventHandler to System.Action<int>. Even though people tend to propagate extending System.EventArgs class and writing custom delegates for handling events, using System.Action<T> is much easier to grasp for beginner.
So let's go with System.Action<int> example now. First, let's change ProgLogic class to be more like this:
public class ProgLogic
{
public event Action<int> SomeValueChanged;
//
// your other code goes here
//
private void OnSomeValueChanged(int newValue)
{
SomeValueChanged?.Invoke(newValue);
}
}
Now, you need to subscribe to the earlier written event in MainWindow class. So we do that as early as possible - in the constructor of MainWindow:
public MainWindow()
{
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += OnSomeValueChanged;
InitializeValues();
}
Then, you describe your logic in the OnSomeValueChanged callback method, like:
private void OnSomeValueChanged(int newValue)
{
TextBoxSomeValue.text = newValue.ToString();
}
Make sure you unsubscribe from the event once MainWindow is getting destroyed to prevent memory leakage. This is just bare-bones for whole logic. I've left some space for interpretation. ;)
I'm not sure if I'm understanding the main point of your question but if you want to create a new value and have that value saved as the default value then you should create a string in your application setting and call on it on text changed.
At the top of your visual2019, in the menu options. open the debug menu and at the bottom you will see ("Your project name" + properties)
2.You will be brought into a new window with menu options on the left, go to the settings.
3.Create a string and set the value to "Some random text"
Note: In the example I placed one text box in front of the other, though this in not a great method it will prevent the text from appearing as a double or drawing a blank
Settings String Example
xaml
<Window x:Class="SaveNewText.MainWindow"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox x:Name="DefaultText" Height="250" Width="250"
Background="Transparent"
Foreground="Black" MouseDown="TextBlock_MouseDown" IsReadOnly="True"/>
<TextBox x:Name="NewText" Height="250" Width="250" Background="Transparent"
Foreground="Black" TextChanged="NewText_TextChanged"/>
</Grid>
</Window>
xaml.cs
namespace SaveNewText
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DefaultText.Text = Properties.Settings.Default.TextString;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
NewText.Focus();
}
private void NewText_TextChanged(object sender, TextChangedEventArgs e)
{
Properties.Settings.Default.TextString = NewText.Text;
Properties.Settings.Default.Save();
DefaultText.Text = Properties.Settings.Default.TextString;
}
}
}
I have a Xamarin page in which I had to use Android native page renderer in order to support platform specific API.
BasePage.xaml passes control to MyPage.xaml with Navigation.PushAsync()
XAML page : MyPage.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="Views.MyPage" Title="My Page">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>
Android Custom page renderer for the above is something like below.
[assembly: ExportRenderer(typeof(MyPage), typeof(MyPageRenderer))]
namespace MyApp.Droid.Renderers
{
public class MyPageRenderer : PageRenderer
{
private Context _localContext;
private global::Android.Views.View view;
private Activity activity;
public event EventHandler ItemAdded;
public MyPageRenderer(Context context) : base(context)
{
_localContext = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
SetupUserInterface();
SetupEventHandlers();
AddView(view);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#"ERROR: ", ex.Message);
}
}
private void SetupUserInterface()
{
activity = this.Context as Activity;
view = activity.LayoutInflater.Inflate(Resource.Layout.axml_layout, this, false);
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
view.Measure(msw, msh);
view.Layout(0, 0, r - l, b - t);
}
private void SetupEventHandlers()
{
//blah blah
}
private void ButtonTapped(object sender, EventArgs e)
{
//do something
//Here Navigate back to page which triggered this with outcome parameter or some event
ItemAdded(this, EventArgs.Empty);
}
}
}
My intention is send back control to MyPage.xaml.cs or BasePage.xaml.cs from MyPageRenderer with outcome of ButtonTapped.I am using event ItemAdded and handle it in code behind of that page. I can not access ItemAdded event which is in android specific renderer only from shared project.
I have to update ViewModel of BasePage so that I update the content of the items there when MyPage has been popped after adding new item by back button.
Problem:
I can access MyPage and BasePage but can not access renderer method and variables from Shared project because Android project depends on shared not vice versa.
I have to do something like below which is working for non-native render page
BasePage:
var myPage = new MyPage();
myPage.ItemAdded += OnItemAdded;
await Navigation.PushAsync(myPage);
MyPage:
public event EventHandler ItemAdded;
.
.
void SomeMethod(){
ItemAdded(this, EventArgs.Empty);
}
Question: How do we pass control from NativeRenderer back to Xamarin Forms shared code?
I know we can pass control to MainActivity class but I want to pass control to BasePage.xaml.cs which I did not get from documentation. If anyone has worked on PageRenderer please suggest.
in "MyPage" Class
public class MyPage : ContentPage
{
public void RaiseSomeButtonClicked() => OnSomeButtonClickeded();
private void OnSomeButtonClicked()
{
//by using aggregators you can publish any event and subscribe it in you BasePage.xaml.cs
((App)App.Current).Container.Resolve<IEventAggregator>()
.GetEvent<SomeButtonClickedEvent>().Publish(new SomeButtonClickedEvent());
}
}
in "MyPageRenderer" Class :
public class MyPageRenderer : PageRenderer
{
MyPage myPage;
//...
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
myPage = (MyPage)e.NewElement;
//...
}
private void ButtonTapped(object sender, EventArgs e)
{
//do something
myPage.RaiseSomeButtonClicked();
}
}
in "BasePage.xaml.cs", subscribe this event.
public partial class BasePage : ContentPage
{
private readonly SubscriptionToken _SomeButtonClickedEventSubscription;
public BasePage()
{
InitializeComponent();
_SomeButtonClickedEventSubscription = eventAggregator.Value.GetEvent<SomeButtonClickedEvent>().SubscribeAsync(async e =>
{
//anything you want to do when button clicked!
}, threadOption: ThreadOption.UIThread, keepSubscriberReferenceAlive: true);
}
}
You should define Your event class in this way:
public class SomeButtonClickedEvent : PubSubEvent<SomeButtonClickedEvent>
{
//you can define parameters here, if the event needs to pass a parameter.
}
With reference to zohre moradi's answer I could achieve this as below.
This does not use IEventAggregator -Subscribe/Publish of events methods. If event is only required at one page IEventAggregator can be avoided.
MyPage.xaml.cs
public event EventHandler ItemAdded;
public void RaiseItemAdded()
{
ItemAdded(this, EventArgs.Empty);
}
//have to close return call back after item addition in MyPage
public async void CallPopAsync()
{
await Navigation.PopAsync();
}
MyPageRenderer.cs
MyPage mypage;
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
mypage= (MyPage)e.NewElement;
if (e.OldElement != null || Element == null)
{
return;
}
try
{
SetupUserInterface();
SetupEventHandlers();
AddView(view);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#"ERROR: ", ex.Message);
}
}
private void ButtonTapped(object sender, EventArgs e)
{
//do something
myPage.RaiseItemAdded();
//notify
Toast.MakeText(this.Context, "Item created", ToastLength.Long).Show();
myPage.CallPopAsync();
}
And in BasePage.xaml.cs
//in some method
var myPage = new MyPage();
myPage.ItemAdded += OnItemAdded;
await Navigation.PushAsync(myPage);
private void OnItemAdded(object sender, EventArgs e)
{
//call method to update binding object of viewmodel
}
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 desire the following:
When clickee a button on the form, I want to handle the events are another class. Thus, the form contains only controls.
It's almost like a MVC pattern: Controller I have a class, and a class RegistrarTrabajador (Model). When controller detects an event of the form, passes the task to the model.
Here the Controller class and the form:
Controller:
namespace RegistroDeUsuarios
{
public class Controller
{
private MainWindow vista;
private RegistrarTrabajador modelo;
public Controller()
{
}
public Controller(MainWindow vista, RegistrarTrabajador modelo)
{
this.vista = vista;
this.modelo = modelo;
}
public void btnRegistrar_Click(Object sender, RoutedEventArgs e)
{
Trabajador trabajador = new Trabajador();
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
trabajador.setPrimerApellido(vista.txtPrimerApellido.Text);
trabajador.setSegundoApellido(vista.txtSegundoApellido.Text);
trabajador.setRangoTrabajador(vista.cboRangoTrabajador.SelectedItem.ToString());
trabajador.setFechaNacimiento(vista.txtFechaNacimiento.Text);
modelo.registrarTrabajador(trabajador);
}
public void btnNuevo_Click(Object sender, RoutedEventArgs e)
{
vista.clean();
}
public void btnSalir_Click(Object sender, RoutedEventArgs e)
{
//Application.Current.Shutdown();
}
}
}
GUI:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboRangoTrabajador.Items.Add("Administrador");
cboRangoTrabajador.Items.Add("Vendedor");
cboRangoTrabajador.Items.Add("Contador");
cboRangoTrabajador.Items.Add("Tecnico Mantenimiento");
cboRangoTrabajador.Items.Add("Programador");
cboRangoTrabajador.Items.Add("Analista");
cboRangoTrabajador.SelectedIndex = 0;
}
public void setControlador(Controller controlador)
{
controlador.btnRegistrar_Click(controlador,new RoutedEventArgs());
controlador.btnNuevo_Click(controlador,new RoutedEventArgs());
controlador.btnSalir_Click(controlador,new RoutedEventArgs());
}
public void clean()
{
txtPrimerNombre.Clear();
txtSegundoNombre.Clear();
txtPrimerApellido.Clear();
txtSegundoApellido.Clear();
txtFechaNacimiento.Clear();
cboRangoTrabajador.SelectedItem = "Administrador";
txtPrimerNombre.Focus();
}
}
You don't use MVC in WPF. You use Model-View-ViewModel (MVVM)
And
you don't create or manipulate UI elements in procedural code in WPF. That's what XAML is for.
Please read about DataBinding,
things like this:
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
are horrible and should NEVER be done in WPF.
Also, your code smells like crappy java. Instead of methods like setPrimerNombre() you should really use Properties. WPF has support for two way databinding to properties, so you don't need to do all this piping manually.
To make this clear, here is a small example:
XAML:
<StackPanel>
<TextBox Text="{Binding Model.LastName}"/>
<TextBox Text="{Binding Model.FirstName}"/>
<Button Content="Registrar" Click="Registrar_Click"/>
<Button Content="Clear" Click="Clear_Click"/>
</StackPanel>
Code Behind:
public class MainWindow: Window
{
public MainViewModel ViewModel { get { return DataContext as MainViewModel; } }
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
public void Registrar_Click(object sender, RoutedEventArgs e)
{
ViewModel.Registrar();
}
public void Clear_Click(object sender, RoutedEventArgs e)
{
ViewModel.Clear();
}
}
ViewModel:
public class MainViewModel: ViewModelBase //You should have some ViewModelBase implementing INotifyPropertyChanged, etc
{
private Trabajador _model;
public Trabajador Model
{
get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
public void Registrar()
{
DataAccessLayer.Registrar(Model);
}
public void Clear()
{
Model = new Trabajador();
}
}
Model:
public class Trabajador: ModelBase //ModelBase Should also implement INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
}
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
}
}
//... And so on.
}
Not sure to have fully understand what you mean but i think you want to know what it is the best way of building a WPF application in layers.
If that's right then MVVM pattern is definitly what you are looking for. Here is a great link to understand how it works and to begin to play with it!