I have moved xamrin.form.maps to MVVM and have set my xaml to
<viewModels:CustomMap IsShowingUser="True" x:Name="customMap" MapPosition="{Binding MyPosition}" CustomPins="{Binding PinCollection}" />
My customMap Class
public static readonly BindableProperty MapPositionProperty = BindableProperty.Create(
nameof(MapPosition),
typeof(Position),
typeof(CustomMap),
new Position(0, 0),
propertyChanged: (b, o, n) =>
{
Console.WriteLine("here");
((CustomMap)b).MoveToRegion(MapSpan.FromCenterAndRadius(
(Position)n, Distance.FromMiles(.05)));
});
public Position MapPosition
{
get { return (Position)GetValue(MapPositionProperty); }
set { SetValue(MapPositionProperty, value); }
}
From page one, I am sending a lat lng to the map. If I set MapPosition in my constructor in my VM it works perfect, the problem is getting the lat and lng to my constructor before everything is bound, other wise it will not update.
In the view model I am collecting the parameters to set the position.
public override async void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("mapLocation"))
{
var mapLocation = parameters.GetValue<MapLocation>("mapLocation");
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
}
}
This has nothing to do with the time when your property is updated.
You bound MapPosition to MyPosition, which I'd guess is a property. For MVVM to work, this MapPosition has to notify that it has changed, usually this looks something like (the the documentation of INotifyPropertyChanged.PropertyChanged)
public MapLocation MyPosition
{
get => myPosition;
set
{
if(myPosition == value)
{
return;
}
myPosition = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string memberName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Your view does know that if the bound objects implements INotifyPropertyChanged it has to subscribe to PropertyChanged and can update its state accordingly if this event is raised.
Now why doesn't it work in your case?
You are not setting MyPosition, allowing the setter to raise PropertyChanged and thus the view to update its state, but you are directly setting the backing field myPosition. In this case, the setter never is called and the view has no chance to know that the value of MyPosition has changed, therefor won't update its state.
If your setter implements the pattern shown above, just change
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
to
MyPosition = new Position(mapLocation.Lat, mapLocation.Lng);
and it should work. Otherwise implement the change notification pattern in MyPosition, too.
Related
I am coding a Xamarin app and doing my best to adhere to MVVM, which I actually really like
I commonly have ContentPages containing references to Views.
I set the binding context to a VM in the Page, and then make use of OnBindingContextChanged in the view
This allows me to use PropertyChanged method to then respond to display logic conditions for my View
I've used it several times successfully but I am baffled why an additional implementation isn't working
Page looks like this
public partial class BindingTextPage : ContentPage
{
public BindingTextPage()
{
InitializeComponent();
this.BindingContext = new ViewModels.LocationsViewModel();
}
}
View looks like this
private LocationsViewModel_vm;
public BindingTestView()
{
InitializeComponent();
System.Diagnostics.Debug.WriteLine("Debug: Initialised BindingTesView view");
}
protected override void OnBindingContextChanged()
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: OnBindingContextChanged: Context " + this.BindingContext.GetType());
_vm = BindingContext as LocationsViewModel;
_vm.PropertyChanged += _vm_PropertyChanged;
}
private void _vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
try
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: Method called");
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: Property " + e.PropertyName);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTestView: Error changing context " + ex.Message);
}
}
Extract of view model, very simply in this case setting a string and hence changing a property, which I would have expected would then cause PropertyChange to fire?
public LocationsViewModel()
{
tempMessage = "this is from the view model";
}
public string tempMessage
{
get
{
return _tempMessage;
}
set
{
_tempMessage = value;
OnPropertyChanged(nameof(tempMessage));
}
}
My debug statements when it boots up shows that OnBindingContextChange is being called, but in this one instance _vm_PropertyChanged never fires? I'd expect tempMessage being set to do so?
The order of events in your code is the following
Constructor of LocationsViewModel is called
From your constructor, you are setting tempMessage
The setter of tempMessage calls OnPropertyChanged, since the event is null at the time being, it's not fired
Constructor of LocationsViewModel is left
Page.BindingContext is set
OnBindingContextChanged is called
LocationsViewModel.PropertyChanged is subscribed by your page
Since the event is raised (or it's tried to) before your page subscribes to, your page simply does not get informed about the event being raised. If you set the value after the event has been subscribed to, the handler will be called as expected.
e.g.
protected override void OnBindingContextChanged()
{
_vm = BindingContext as LocationsViewModel;
_vm.PropertyChanged += _vm_PropertyChanged;
_vm.tempMessage = "Hello, world!"; // clichée , I know
}
So when writing UI in GTK it's generally preferrable to handle reading of files, etc. in an Async Method. things such as listboxes, are generally bound to a ListModel, the items in the ListBox updated in accordance with the items_changed signal.
So if I have some class, that implements ListModel, and has an add function, and some FileReader that holds a reference to said ListModel, and call add from an async function, how do i make that in essence triggering the items_changed and having GTK update accordingly?
I've tried list.items_changed.connect(message("Items changed!")); but it never triggers.
I saw this: How can one update GTK+ UI in Vala from a long operation without blocking the UI
but in this example, it's just the button label that is changed, no signal is actually triggered.
EDIT: (Codesample added at the request of #Michael Gratton
//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.
//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging
public class AsyncFileContext : Object{
private int64 offset;
private bool start_read;
private bool read_to_end;
private Factories.IVCardFactory factory;
private File file;
private FileMonitor monitor;
private Gee.Set<IVCard> vcard_buffer;
private IObservableSet<IVCard> _vCards;
public IObservableSet<IVCard> vCards {
owned get{
return this._vCards;
}
}
construct{
//We want to start fileops at the beginning of the file
this.offset = (int64)0;
this.start_read = true;
this.read_to_end = false;
this.vcard_buffer = new Gee.HashSet<IVCard>();
this.factory = new Factories.GenericVCardFactory();
}
public void add_vcard(IVCard card){
//TODO: implement
}
public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
this._vCards = vcards;
this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
this.file = File.new_for_path(path);
this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
message("1");
//TODO: add connect
this.monitor.changed.connect((file, otherfile, event) => {
if(event != FileMonitorEvent.DELETED){
bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
Idle.add(() => {
read_file_async.begin(changes_done);
return false;
});
}
});
message("2");
//We don't know that changes are done yet
//TODO: Consider carefully how you want this to work when it is NOT called from an event
Idle.add(() => {
read_file_async.begin(false);
return false;
});
}
//Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
private async void read_file_async(bool changes_done) throws IOError{
if(!this.start_read){
return;
}
this.start_read = false;
var dis = new DataInputStream(yield file.read_async());
message("3");
//If we've been reading this file, and there's then a change, we assume we need to continue where we let off
//TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
if(offset > 0){
dis.seek(offset, SeekType.SET);
}
string line;
int vcards_added = 0;
while((line = yield dis.read_line_async()) != null){
message("position: %s".printf(dis.tell().to_string()));
this.offset = dis.tell();
message("4");
message(line);
//if the line is empty, we want to jump to next line, and ignore the input here entirely
if(line.chomp().chug() == ""){
continue;
}
this.factory.add_line(line);
if(factory.vcard_ready){
message("creating...");
this.vcard_buffer.add(factory.create());
vcards_added++;
//If we've read-in and created an entire vcard, it's time to yield
message("Yielding...");
Idle.add(() => {
_vCards.add_all(vcard_buffer);
vcard_buffer.remove_all(_vCards);
return false;
});
Idle.add(read_file_async.callback);
yield;
message("Resuming");
}
}
//IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
if(changes_done){ //|| vcards_added == 0){
this.offset = 0;
}
this.start_read = true;
}
}
//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{
private Gee.List<IVCard> vcard_list;
private IObservableCollection<IVCard> vcard_collection;
public VCardListModel(IObservableCollection<IVCard> vcard_collection){
this.vcard_collection = vcard_collection;
this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());
this.vcard_collection.item_added.connect((vcard) => {
vcard_list.add(vcard);
int pos = vcard_list.index_of(vcard);
items_changed(pos, 0, 1);
});
this.vcard_collection.item_removed.connect((vcard) => {
int pos = vcard_list.index_of(vcard);
vcard_list.remove(vcard);
items_changed(pos, 1, 0);
});
this.vcard_collection.cleared.connect(() => {
items_changed(0, vcard_list.size, 0);
vcard_list.clear();
});
}
public Object? get_item(uint position){
if((vcard_list.size - 1) < position){
return null;
}
return this.vcard_list.get((int)position);
}
public Type get_item_type(){
return Type.from_name("VikingvCardIVCard");
}
public uint get_n_items(){
return (uint)this.vcard_list.size;
}
public Object? get_object(uint position){
return this.get_item((int)position);
}
}
//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{
private ListModel list_model;
public ContactList(IObservableCollection<IVCard> ivcards){
this.list_model = new VCardListModel(ivcards);
bind_model(this.list_model, create_row_func);
list_model.items_changed.connect(() => {
message("Items Changed!");
base.show_all();
});
}
private Gtk.Widget create_row_func(Object item){
return new ContactRow((IVCard)item);
}
}
Heres the way i 'solved' it.
I'm not particularly proud of this solution, but there are a couple of awful things about the Gtk ListBox, one of them being (and this might really be more of a ListModel issue) if the ListBox is bound to a ListModel, the ListBox will NOT be sortable by using the sort method, and to me at least, that is a dealbreaker. I've solved it by making a class which is basically a List wrapper, which has an 'added' signal and a 'remove' signal. Upon adding an element to the list, the added signal is then wired, so it will create a new Row object and add it to the list box. That way, data is control in a manner Similar to ListModel binding. I can not make it work without calling the ShowAll method though.
private IObservableCollection<IVCard> _ivcards;
public IObservableCollection<IVCard> ivcards {
get{
return _ivcards;
}
set{
this._ivcards = value;
foreach(var card in this._ivcards){
base.prepend(new ContactRow(card));
}
this._ivcards.item_added.connect((item) => {
base.add(new ContactRow(item));
base.show_all();
});
base.show_all();
}
}
Even though this is by no means the best code I've come up with, it works very well.
I need to recollect some data calling to a method is connecting to a webservice.
problem: Imagine I need to update the content text of a label control according to this remote gathered information. Until all this data is recollected I'm not going to be able to show the label.
desired: I'd like to first show the label with a default text, and as I'm receiving this information I want to update the label content (please, don't take this description as a sucked code, I'm trying to brief my real situation).
I'd like to create an observable sequence of these methods. Nevertheless, these method have not the same signature. For example:
int GetInt() {
return service.GetInt();
}
string GetString() {
return service.GetString();
}
string GetString2 {
return service.GetString2();
}
These methods are not async.
Is it possible to create an observable sequence of these methods?
How could I create it?
Nevertheless, which's the best alternative to achieve my goal?
Creating custom observable sequences can be achieved with the Observable.Create. An example using your requirements is shown below:
private int GetInt()
{
Thread.Sleep(1000);
return 1;
}
private string GetString()
{
Thread.Sleep(1000);
return "Hello";
}
private string GetString2()
{
Thread.Sleep(2000);
return "World!";
}
private IObservable<string> RetrieveContent()
{
return Observable.Create<string>(
observer =>
{
observer.OnNext("Default Text");
int value = GetInt();
observer.OnNext($"Got value {value}. Getting string...");
string string1 = GetString();
observer.OnNext($"Got string {string1}. Getting second string...");
string string2 = GetString2();
observer.OnNext(string2);
observer.OnCompleted();
return Disposable.Empty;
}
);
}
Note how I have emulated network delay by introducing a Thread.Sleep call into each of the GetXXX methods. In order to ensure your UI doesn't hang when subscribing to this observable, you should subscribe as follows:
IDisposable subscription = RetrieveContent()
.SubscribeOn(TaskPoolScheduler.Default)
.ObserveOn(DispatcherScheduler.Current)
.Subscribe(text => Label = text);
This code uses the .SubscribeOn(TaskPoolScheduler.Default) extension method to use a TaskPool thread to start the observable sequence and will be blocked by the calls the Thread.Sleep but, as this is not the UI thread, your UI will remain responsive. Then, to ensure we update the UI on the UI thread, we use the ".ObserveOn(DispatcherScheduler.Current)" to invoke the updates onto the UI thread before setting the (data bound) Label property.
Hope this is what you were looking for, but leave a comment if not and I'll try to help further.
I would look at creating a wrapper class for your service to expose the values as separate observables.
So, start with a service interface:
public interface IService
{
int GetInt();
string GetString();
string GetString2();
}
...and then you write ServiceWrapper:
public class ServiceWrapper : IService
{
private IService service;
private Subject<int> subjectGetInt = new Subject<int>();
private Subject<string> subjectGetString = new Subject<string>();
private Subject<string> subjectGetString2 = new Subject<string>();
public ServiceWrapper(IService service)
{
this.service = service;
}
public int GetInt()
{
var value = service.GetInt();
this.subjectGetInt.OnNext(value);
return value;
}
public IObservable<int> GetInts()
{
return this.subjectGetInt.AsObservable();
}
public string GetString()
{
var value = service.GetString();
this.subjectGetString.OnNext(value);
return value;
}
public IObservable<string> GetStrings()
{
return this.subjectGetString.AsObservable();
}
public string GetString2()
{
var value = service.GetString2();
this.subjectGetString2.OnNext(value);
return value;
}
public IObservable<string> GetString2s()
{
return this.subjectGetString2.AsObservable();
}
}
Now, assuming that you current service is called Service, you would write this code to set things up:
IService service = new Service();
ServiceWrapper wrapped = new ServiceWrapper(service); // Still an `IService`
var subscription =
Observable
.Merge(
wrapped.GetInts().Select(x => x.ToString()),
wrapped.GetStrings(),
wrapped.GetString2s())
.Subscribe(x => label.Text = x);
IService wrappedService = wrapped;
Now pass wrappedService instead of service to your code. It's still calling the underlying service code so no need for a re-write, yet you still are getting the observables that you want.
This is effectively a gang of four decorator pattern.
I am currently programming using the MVVM pattern.
My view Model Looks like so
class DoorsViewModel
{
ObservableCollection<Door> doorCollection;
};
the door class looks like the following
class Door
{
string name;
bool isOpen;
};
my view is linked to the viewmodel, and simply contains a longlistselector with a picture and the name of the door. I want the picture to be dynamic and change depending on the state of the door( whether it's open or closed ). How would i implement it so that the picture updates dynamically depending on the state of the door? should this be done in the viewmodel? or should it be done within the view?
This logic should be in the ViewModel. All logic that is related to the view or how things are displayed should be in the ViewModel. No logic should be in the view (.xaml.cs).
You typically use the INotifyPropertyChanged interface to notify the View that something has changed. In this case you want a door image to be changed when the state of the door changes. In this case I would try something like this.
class Door: INotifyPropertyChanged
{
private string _name;
private bool _isOpen;
public Uri DoorImage
{
get
{
if (_isOpen) return new Uri("uri_to_open.png");
return new Uri("uri_to_closed.png");
}
}
public bool IsOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
RaisePropertyChanged("IsOpen");
// important, notifies the UI to update the door image
RaisePropertyChanged("DoorImage");
}
}
private void RaisePropertyChanged(string propertyName)
{
var tmp = PropertyChanged;
if (tmp != null) tmp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
};
Note: I have encapsulated the fields into properties.
If your image is embedded in your assebmly, please check out this link to learn how to write an uri for your image.
I am new to MvvmCross and I have a question.
I noticed that the following binding code works in one way only:
{ this, "{'CurrentIndex':{'Path':'CurrentIndex','Mode':'TwoWay'}}" }
CurrentIndex is an Int Property in the View
CurrentIndex is also an Int Property in the ViewModel
This way works!
ViewModel => View
But not this way!
View => ViewModel
I have a collection of ViewControllers and my goal was to call a DeleteCommand for the CurrentIndex in the viewModel.
However,
"Android and Touch 2 way bindings are incomplete"
Reference: MvvmCross experiences, hindsight, limitations?
My guess is the TwoWay mode only works for Controls (UILabel, UITextfield, ...) but not for Properties.
So, is there a good way to make it works in both ways? Or Are there any alternatives to my problem?
Patrick
In order for a binding to transfer any value between a View to a ViewModel, then it needs to hook into some event when the value changes.
In the ViewModel, this event is always the event in the INotifyProperty interface.
In the View/Activity, there is one single pattern employed - so each binding has to hook into a separate event. For example, the Text on EditText is hooked up using the TextChanged event (see MvxEditTextTextTargetBinding.cs) while the value in a SeekBar is hooked up using a Listener object rather than an event (see MvxSeekBarProgressTargetBinging.cs).
So if you wanted to implement this two-way binding for your activity, then you could do this by:
declaring an event - CurrentIndexChanged - in your activity (MyActivity) which is fired whenever CurrentIndex changes
declare a custom binding for your MyActivity which programmatically links CurrentIndex and CurrentIndexChanged
adding the custom binding to the binding registry during Setup
For example, your activity might include:
public event EventHandler CurrentIndexChanged;
private int _currentIndex;
public int CurrentIndex
{
get { return _currentIndex; }
set { _currentIndex = value; if (CurrentIndexChanged != null) CurrentIndexChanged(this, EventArgs.Empty); }
}
And you might then declare a binding class like:
public class MyBinding : MvxPropertyInfoTargetBinding<MyActivity>
{
public MyBinding (object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
View.CurrentIndexChanged += OnCurrentIndexChanged;
}
public override MvxBindingMode DefaultMode
{
get
{
return MvxBindingMode.TwoWay;
}
}
private void OnCurrentIndexChanged(object sender, EventArgs ignored)
{
FireValueChanged(View.CurrentIndex);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
View.CurrentIndexChanged -= OnCurrentIndexChanged;
}
}
}
And you'd need to tell the binding system about this binding in setup like:
registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(MyBinding), typeof(MyActivity), "CurrentIndex"));
However... at a practical level, if you are operating in C# rather than in XML, then you might be better off in this case using C# to simply update the ViewModel rather than using declarative binding in this case.
To be clear... in this case, I would most probably just write the Activity property as:
public int CurrentIndex
{
get { return _currentIndex; }
set { _currentIndex = value; ViewModel.CurrentIndex = value; }
}
Or... I'd consider not having this property in the Activity at all.
If it helps, there's some more information on custom bindings in:
MonoTouch MVVMCross binding to instance variables
In MvvmCross how do I do custom bind properties
Hope this helps! IMHO the bindings are there to help you when you're working in XML - you don't have to use them...
Stuart
UPDATE If you are going to do lots of these and follow the same name pattern - using property named X with changed EventHandler event named XChanged then something like this might work - it uses reflection to find the event automagically:
public class MyBinding<T> : MvxPropertyInfoTargetBinding<T>
where T : class
{
private readonly PropertyInfo _propertyInfo;
private readonly EventInfo _eventInfo;
public MyBinding(object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
_propertyInfo = targetPropertyInfo;
var eventName = _propertyInfo.Name + "Changed";
_eventInfo = View.GetType().GetEvent(eventName);
if (_eventInfo == null)
{
throw new MvxException("Event missing " + eventName);
}
if (_eventInfo.EventHandlerType != typeof(EventHandler))
{
throw new MvxException("Event type mismatch for " + eventName);
}
var addMethod = _eventInfo.GetAddMethod();
addMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
}
public override MvxBindingMode DefaultMode
{
get
{
return MvxBindingMode.TwoWay;
}
}
private void OnChanged(object sender, EventArgs ignored)
{
var value = _propertyInfo.GetValue(View, null);
FireValueChanged(value);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
var removeMethod = _eventInfo.GetRemoveMethod();
removeMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
}
}
}