I am using ReactiveUI in a Xamarin.Forms project but when I press a button I get the error: "Only the original thread that created a view hierarchy can touch its views. '"
Here is my code.
App.xaml.cs
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
AppShell.xaml.cs
public partial class AppShell : Shell
{
Random rand = new Random();
Dictionary<string, Type> routes = new Dictionary<string, Type>();
public Dictionary<string, Type> Routes { get { return routes; } }
public AppShell()
{
InitializeComponent();
RegisterRoutes();
BindingContext = this;
}
void RegisterRoutes()
{
routes.Add("monkeydetails", typeof(HomeView));
foreach (var item in routes)
{
Routing.RegisterRoute(item.Key, item.Value);
}
}
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
// Cancel any back navigation
//if (e.Source == ShellNavigationSource.Pop)
//{
// e.Cancel();
//}
}
void OnNavigated(object sender, ShellNavigatedEventArgs e)
{
}
}
By default you go to the HomeView view
HomeView.xaml
<Button Text="Pulsar" x:Name="Boton"></Button>
HomeView.xaml.cs
public partial class HomeView : ReactiveContentPage<HomeViewModel>
{
protected CompositeDisposable ControlBindings { get; } = new CompositeDisposable();
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
public HomeView()
{
InitializeComponent();
this.ViewModel = new HomeViewModel();
this.BindCommand(ViewModel, vm => vm.Navigate, view => view.Boton);
}
}
HomeViewModel.cs
public class HomeViewModel : ViewModelBase, IRoutableViewModel
{
int prueb = 0;
public HomeViewModel()
{
Navigate = ReactiveCommand.CreateFromTask(async() =>
{
await hola();
});
}
public async Task hola()
{
}
public string prueba()
{
return prueb.ToString();
}
public IObservable<string> NumberStream { get; }
public string UrlPathSegment => "Number Stream Page";
public IScreen HostScreen { get; }
public override string Id => "Pass Parameter";
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
}
I can't understand why the error appears when I press the button
Related
I'm writing a wpf application, where I have music albums and corresponding songs. I can add albums and corresponding songs. But now I want to to refresh the view when a change to the database is made. I found many possible solutions, but as I'm new to wpf and c# I don't know which one would suite my code.
In my MainView have an album list and a add button which opens another window where I can add data with a textbox.
AlbumListViewModel
#region Constants
IWindowManager addAlbum = new WindowManager();
IWindowManager addSong = new WindowManager();
private AlbumViewModel _selectedAlbum;
private SongViewModel _selectedSong;
#endregion
#region Constructor
public AlbumListViewModel()
{
Albums = new ObservableCollection<AlbumViewModel>(GetAlbumList());
AddAlbumCommand = new RelayCommand(x => AddAlbum());
AddSongCommand = new RelayCommand(x => AddSong());
}
#endregion
#region Properties
public ICommand AddAlbumCommand { get; private set; }
public ICommand AddSongCommand { get; private set; }
public ObservableCollection<AlbumViewModel> Albums { get; set; }
public AlbumViewModel SelectedAlbum
{
get
{
return _selectedAlbum;
}
set
{
if (_selectedAlbum != value)
{
_selectedAlbum = value;
}
NotifyPropertyChanged("SelectedAlbum");
}
}
public SongViewModel SelectedSong
{
get
{
return _selectedSong;
}
set
{
if (_selectedSong != value)
{
_selectedSong = value;
}
NotifyPropertyChanged("SelectedSong");
}
}
#endregion
#region Methods
public List<AlbumViewModel> GetAlbumList()
{
var controller = new BandManagerController();
return controller.GetAlbumList()
.Select(a => new AlbumViewModel(a))
.ToList();
}
private void AddAlbum()
{
addAlbum.ShowDialog(new AlbumViewModel(new AlbumData()));
}
private void AddSong()
{
addSong.ShowDialog(new SongViewModel(new SongData { AlbumID = SelectedAlbum.AlbumID }));
}
It opens the AlbumView where I add albums to the database.
public class AlbumViewModel : Screen
{
#region Constants
private AlbumData _data;
#endregion
#region Constructor
public AlbumViewModel(AlbumData data)
{
_data = data;
SongListVM = new SongListViewModel(data.AlbumID);
SaveAlbumToDatabase = new RelayCommand(x => AlbumToDatabase(data));
}
#endregion
#region Properties
public SongListViewModel SongListVM { get; set; }
public ICommand SaveAlbumToDatabase { get; private set; }
public string AlbumName
{
get
{
return _data.AlbumName;
}
set
{
if (_data.AlbumName != value)
{
_data.AlbumName = value;
NotifyOfPropertyChange("AlbumName");
}
}
}
public int AlbumID
{
get
{
return _data.AlbumID;
}
set
{
if (_data.AlbumID != value)
{
_data.AlbumID = value;
NotifyOfPropertyChange("AlbumID");
}
}
}
public string AlbumYear
{
get
{
return _data.AlbumYear;
}
set
{
if (_data.AlbumYear != value)
{
_data.AlbumYear = value;
NotifyOfPropertyChange("AlbumYear");
}
}
}
#endregion
#region Methods
public AlbumData AddAlbumEntry(AlbumData albumData)
{
var controller = new BandManagerController();
return controller.AddAlbumEntry(albumData);
}
public void ExecuteCancelCommand()
{
(GetView() as Window).Close();
}
public void AlbumToDatabase(AlbumData data)
{
AddAlbumEntry(data);
ExecuteCancelCommand();
}
#endregion
}
The AddAlbumEntry Method in the ALbumView is in a different class which is the connections to my database. I already use an ObservableCollection but don't know how to tell it the Database was updated.
Thanks in advance!
Just want to answer my question. I just changed my AddAlbum method to use a Deactivated event, to reload the Collection after the Dialog closes like:
private void AddAlbum()
{
var vm = new AlbumViewModel(new AlbumData());
vm.Deactivated += (s, e) => GetAlbumList();
addAlbum.ShowDialog(vm);
}
I would like to use MVP Design pattern for a WinForm App but i'm facing the problem of calling a View Update from another thread.
Here's my code
MODEL
public class Model : IModel
{
public string Status { get; set; }
public async void LongOperation(IHomeView View)
{
for (int i = 0; i < 1000; i++)
{
View.StatusListView = i.ToString();
}
}
}
PRESENTER
public class HomePresenter
{
IHomeView _IView;
IModel _IModel;
Model _Model = new Model();
public HomePresenter(IHomeView IView)
{
_IView = IView;
}
public async void LaunchLongOperation()
{
await Task.Run(() => _Model.LongOperation(_IView));
}
}
INTERFACE VIEW-PRESENTER
public interface IHomeView
{
string StatusListView { get; set; }
}
INTERFACE PRESENTER-MODEL
public interface IModel
{
string Status { get; set; }
}
FORM:
public partial class frmMain : Form, IHomeView
{
HomePresenter _Presenter;
public frmMain()
{
InitializeComponent();
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void btnAvvia_Click(object sender, EventArgs e)
{
_Presenter.launchLongOperation();
}
}
i would like to update a list view in the Main form during the long operations of the Model class.
Which is the best way to do that?
Try this code without debugging, you'll be surprised about it works!
The quick and dirty way to make it work in debugging mode as well is to add Control.CheckForIllegalCrossThreadCalls = false; into the constructor of your form.
public partial class MainForm : Form, IHomeView
{
HomePresenter _Presenter;
public MainForm()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false; //<-- add this
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void button1_Click(object sender, EventArgs e)
{
_Presenter.LaunchLongOperation();
}
}
I have two bound textboxes in my View.
<TextBox Text="{Binding BookingWizard.CustomerView.Email,Mode=TwoWay}" />
<TextBox Text="{Binding BookingWizard.CustomerView.ContactNo,Mode=TwoWay}" />
I can populate these fields when another textbox has lost its focus. the code behind for that bit is:
private void txtFirstName_LostFocus(object sender, RoutedEventArgs e)
{
LookUpEmailAndContactNo();
}
private void LookUpEmailAndContactNo()
{
var vm = this.DataContext as ApplicationViewModel;
var customer = vm.BookingWizard.LookUpEmailAndContactNo();
//etc
vm.BookingWizard.CustomerView.Email = customer.Email;
}
public Customer LookUpEmailAndContactNo()
{
var res= InformedWorkerBusinessService.Customer.GetEmailAndContactNo(CustomerView.FName, CustomerView.SName);
if (res!=null)
{
CustomerView.Email = res.Email;
CustomerView.ContactNo = res.ContactNo;
}
return CustomerView;
}
This is the screenshot of my data context when i set a break-point in the LookUpEmailAndContactNo event:
As you can see the data context does have these values but I cannot see what is wrong with my UI binding?
ADDITIONAL:
I set my view model at the App entry point:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationView app = new ApplicationView();
ApplicationViewModel context = new ApplicationViewModel();
context.ActiveRecord = new ActiveRecord();
context.CustomerSearch = new CustomerSearch();
context.BookingWizard = new BookingWizard();
context.BookingWizard.CustomerView = new InformedWorkerModel.Customer();
context.BookingWizard.JobView = new InformedWorkerModel.Job();
app.DataContext = context;
app.Show();
}
}
This is inside my BookingWizard class:
public class BookingWizard : ViewModelBase, IDataErrorInfo
{
Customer _Customer;
public bool IsExistingCustomer { get; set; }
public IEnumerable<string> FNames
{
get
{
if (CustomerView.SName == null)
{
CustomerView.SName = string.Empty;
}
return InformedWorkerBusinessService.Customer.GetFirstNames(CustomerView.SName);
}
}
public Customer LookUpEmailAndContactNo()
{
var res= InformedWorkerBusinessService.Customer.GetEmailAndContactNo(CustomerView.FName, CustomerView.SName);
if (res!=null)
{
CustomerView.Email = res.Email;
CustomerView.ContactNo = res.ContactNo;
}
return CustomerView;
}
public Customer CustomerView
{
get { return _Customer; }
set
{
_Customer = value; RaisePropertyChanged("CustomerView");
}
}
}
and in my Customer Class:
[Table("Customer")]
public class Customer
{
[AutoIncrement]
[PrimaryKey]
public int CustomerId { get; set; }
public string SName { get; set; }
public string FName { get; set; }
public string ContactNo { get; set; }
public string Email { get ; set; }
}
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.
I want to add an observer in my model, i try to generic delegate but here is problem when invoke.
Here is my code and it works when I use 'handler.DynamicInvoke(this)' instead of 'Invoke'
but I know DynamicInvoke is slow... I want to know is here a right way to use Invoke.
public class Model<T>
{
public delegate void UpdatePrototype<T>(T mdl);
private List<UpdatePrototype<T>> listeners = new List<UpdatePrototype<T>>();
public void Bind(UpdatePrototype<T> handler)
{
listeners.Add(handler);
}
public void Sync()
{
foreach(UpdatePrototype<T> handler in listeners)
{
handler.Invoke((T)this); // << ERROR: can not convert Model<T> to T
}
}
public string Name = "Model";
}
public class MyModel : Model<MyModel>
{
public string Name = "MyModel";
}
public class YourModel : Model<YourModel>
{
public string Name = "YourModel";
}
void Main()
{
MyModel mdl = new MyModel();
mdl.Bind(MyUpdate);
mdl.Sync();
YourModel your = new YourModel();
your.Bind(YourUpdate);
your.Sync();
}
void MyUpdate(MyModel mdl)
{
Debug.Log(mdl.Name);
}
void YourUpdate(YourModel mdl)
{
Debug.Log(mdl.Name);
}
============
thanks #IVAAAN123 i modify my code as follow.
it is fine for me, although mdl.Sync<MyModel>() has a little odd ;)
public class Model<T>
{
public delegate void UpdatePrototype<T>(T mdl);
private List<UpdatePrototype<T>> listeners = new List<UpdatePrototype<T>>();
public void Bind(UpdatePrototype<T> handler)
{
listeners.Add(handler);
}
public void Sync()
{
foreach(UpdatePrototype<T> handler in listeners)
{
handler.DynamicInvoke(this);
}
}
public void Sync<T>() where T : Model<T>
{
foreach(UpdatePrototype<T> handler in listeners)
{
handler.Invoke((T)this);
}
}
public string Name = "Model";
}
public class MyModel : Model<MyModel>
{
public string Name = "MyModel";
}
public class YourModel : Model<YourModel>
{
public string Name = "YourModel";
}
void Main()
{
MyModel mdl = new MyModel();
mdl.Bind(MyUpdate);
mdl.Sync<MyModel>();
mdl.Sync();
YourModel your = new YourModel();
your.Bind(YourUpdate);
your.Sync<YourModel>();
your.Sync();
}
void MyUpdate(MyModel mdl)
{
Debug.Log(mdl.Name);
}
void YourUpdate(YourModel mdl)
{
Debug.Log(mdl.Name);
}
}
public class Model<T>
{
public delegate void UpdatePrototype<S>(S mdl);
private List<UpdatePrototype<Model<T>>> listeners = new List<UpdatePrototype<Model<T>>>();
public void Bind(UpdatePrototype<Model<T>> handler)
{
listeners.Add(handler);
}
public void Sync()
{
foreach (UpdatePrototype<Model<T>> handler in listeners)
{
handler(this);
}
}
public virtual string Name
{
get
{
return "Model";
}
}
}
public class MyModel : Model<MyModel>
{
public override string Name
{
get
{
return "MyModel";
}
}
}
public class YourModel : Model<YourModel>
{
public override string Name
{
get
{
return "YourModel";
}
}
}
void main()
{
MyModel mdl = new MyModel();
mdl.Bind(MyUpdate);
mdl.Sync();
YourModel your = new YourModel();
your.Bind(YourUpdate);
your.Sync();
}
void MyUpdate(Model<MyModel> mdl)
{
Console.WriteLine("MY MODEL HANDLER");
Console.WriteLine(mdl.Name);
}
void YourUpdate(Model<YourModel> mdl)
{
Console.WriteLine("YOUR MODEL HANDLER");
Console.WriteLine(mdl.Name);
}