I was just reading Rx HOL NET. Upon finding (example uses Windows Forms):
var moves = Observable.FromEvent<MouseEventArgs>(frm, "MouseMove");
I wonder how can I instantiate and pass the reference to moves to ViewModel in some WPF MVVM setup? In my understanding it does make sense to try and filter this stream of data inside ViewModel.
Or, how to do something similar for keyboard input into TextBox? In this scenario you wouldn't, for example, attach some text masking behavior to a control in XAML but would, instead, let Observer in VM filter and validate keyboard input.
Am I completely off the track?
Here is an example of how you could implement the web service dictionary in a MVVM fashion. It has three parts:
The ObservablePropertyBacking class, a backing for properties (of T) that also implements IObservable
The MyViewModel class. It contains a property CurrentText which uses an ObservablePropertyBacking as backing storage. It also observes the value of this property and uses it to call the dictionary web service.
The MainView.xaml which contains a TextBox. Its Text property is two-way bound to the CurrentText property on the view model.
MyViewModel.cs:
class MyViewModel: INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
#endregion
public MyViewModel()
{
SetupProperties();
}
#region CurrentText
/* We use a special class for backing of the CurrentText property. This object
* holds the value of the property and also dispatches each change in an observable
* sequence, i.e. it implements IObservable<T>.
*/
private ObservablePropertyBacking<string> _textInput;
public string CurrentText
{
get { return _textInput.Value; }
set
{
if (value == _textInput.Value) { return; }
_textInput.Value = value;
RaisePropertyChanged("CurrentText");
}
}
#endregion
/* Create property backing storage and subscribe UpdateDictionary to the observable
* sequence. Since UpdateDictionary calls a web service, we throttle the sequence.
*/
private void SetupProperties()
{
_textInput = new ObservablePropertyBacking<string>();
_textInput.Throttle(TimeSpan.FromSeconds(1)).Subscribe(UpdateDictionary);
}
private void UpdateDictionary(string text)
{
Debug.WriteLine(text);
}
}
ObservablePropertyBacking.cs:
public class ObservablePropertyBacking<T> : IObservable<T>
{
private Subject<T> _innerObservable = new Subject<T>();
private T _value;
public T Value
{
get { return _value; }
set
{
_value = value;
_innerObservable.OnNext(value);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
return _innerObservable
.DistinctUntilChanged()
.AsObservable()
.Subscribe(observer);
}
}
MainPage.xaml:
<Window
x:Class="RxMvvm_3435956.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox
Text="{Binding CurrentText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
This might help: Reactive Extensions (Rx) + MVVM = ?
The easiest way of doing the keyboard sample would be to two-way bind the text to a property of the ViewModel. The Text setter could then write to a private Subject that the rest of your code uses as a basis of IObservable<string>. From there, you can complete the HOL sample.
Mouse movements are generally considered too "view" to put in the ViewModel, but if the logic that came off it was complex enough, you could have it execute an ICommand or perhaps put the logic into a behavior. If it were an ICommand, you could have the command have a WhenExecuted IObservable property that you could pick up in your ViewModel.`
Related
I needed to implement a wait indicator for a page in my Maui app.
Searching gave me this, but no step by step instructions.
So how do I do this?
Overview:
The control to display the animation is called ActivityIndicator.
ActivityIndicator is a visual element, should be part of your page.
So, add an ActivityIndicator to your xaml.
The state of the indicator is part of logic - should live in your view model.
So, add a bindable property to your view model, and bind ActivityIndicator.IsRunning to this property.
Sample (I haven't tested, just for illustration)
Page (xaml):
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customcontrols="clr-namespace:Waiter.Maui.CustomControls"
x:Class="..." >
<ContentPage.Content>
<ActivityIndicator IsRunning="{Binding IsBusy}" />
<Button Text="Go" Command="{Binding GoCommand}" />
</ContentPage.Content>
</ContentPage>
ViewModel:
namespace MyNamespace
{
public class MyViewModel : BaseViewModel
{
public MyViewModel()
{
GoCommand = new Command(execute: OnGo, canExecute: true);
}
public Command GoCommand { get; }
private void OnGo()
{
MainThread.InvokeOnMainThreadAsync(async () =>
{
IsBusy = true;
Thread.Sleep(5000);
IsBusy = false;
return result;
});
}
}
}
BaseViewModel class (so that it can be re-used, from existing community content):
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Waiter.Maui.Pages
{
public class BaseViewModel : INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
I want to point out few things.
First of all, this "IsBusy" that I see getting recommended all around, is working strategy. I can only recommend using CommunityToolkit.MVVM, and letting it do your job, and handle all notification code instead of you.
However, using such boolean variable, is no different than using Lock, Mutex, Semaphore, etc. A programmer has to be very careful how and when it is changed, otherwise all kinds of bugs may occur.
In reality, most problems can be solved with commanding itself.
Specifically CanExecute property is more than enough.
I recommend this:
https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/commanding?view=net-maui-7.0
Before becoming slave to manual changing bool variables.
I need to change the Visibility of a Button on a View from method call from within a class.
I have tried accessing the VeiwModel by exposing it in the class, and then had success in changing the Property "ShowRedHat" from true to false, but this does not update the Visibility of the Button in the View. This also double loads the ViewModel, which is not acceptable in my solution.
Any help is appreciated.
The class:
public class HatEngine
{
public void SetShowRedHat()
{
????.ShowRedHat = false;
}
}
The Property in the ViewModel:
public class MyViewModel : ObservableObject
{
private bool _showRedHat;
public bool ShowRedHat
{
get { return _showRedHat; }
set
{
OnPropertyChanged(ref _showRedHat, value);
}
}
}
The Button in the View:
<Button Content="Red Hat"
Command="{Binding RedHatCommand}"
Visibility="{Binding ShowRedHat, Converter={StaticResource BoolToVis}}"/>
If the purpose of HatEngine is to be a service that is used by MyViewModel, then something like the following be the start of getting what you need.
This example uses dependency injection via the constructor; this is common in MVVM and if you're not familiar with it, I would highly recommend looking into it further.
// define delegate for event to be fired from HatEngine instances
public delegate void HatEngineNotifyEventHandler(object sender, bool shouldShow);
// interface declaration for HatEngine - this is important for injecting mocks for unit testing
public interface IHatEngine
{
event HatEngineNotifyEventHandler Notify;
void SetShowRedHat(bool show);
}
// simple IHatEngine implementation
public sealed class HatEngine : IHatEngine
{
public event HatEngineNotifyEventHandler Notify;
public void SetShowRedHat(bool show) => OnNotify(show);
private void OnNotify(bool shouldShow) =>
Notify?.Invoke(this, shouldShow);
}
public class MyViewModel : ObservableObject
{
private readonly IHatEngine _hatEngine;
private bool _showRedHat;
// MyViewModel consumes an IHatEngine instance and subscribes to its Notify event
public MyViewModel(IHatEngine hatEngine = null)
{
// many MVVM frameworks include a DI container that should be used here
// to resolve an IHatEngine instance; however, for simplicity for this
// example just create HatEngine() directly
_hatEngine = hatEngine ?? new HatEngine();
// when the event is received, update ShowRedHat accordingly
_hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;
}
public bool ShowRedHat
{
get => _showRedHat;
set => OnPropertyChanged(ref _showRedHat, value);
}
}
You can just bind an integer since Visibility is an Enum, check documentation since in some versions Hidden option is not available and Collapsed becomes 1, however normally you can just use these below:
Visible [0] - Display the element.
Hidden [1] Do not display the element, but reserve space for the
element in layout.
Collapsed [2] Do not display the element, and do not reserve space for
it in layout.
In order to get PropertyChanged to fire in NUnit tests, I had to set ShouldAlwaysRaiseInpcOnUserInterfaceThread(false). Are there any repercussions to this when I later use the class as a ViewModel? Maybe I should be setting up a user interface thread in NUnit? Help!
public interface ISomething : INotifyPropertyChanged
{
}
public class Something : MvxNotifyPropertyChanged, ISomething
{
public Something()
{
ShouldAlwaysRaiseInpcOnUserInterfaceThread(false);
}
private int _num;
public int Num
{
get { return _num; }
set { if (_num != value) { _num = value; RaisePropertyChanged(() => Num); }
}
}
By default MvvmCross marshals calls like RaisePropertyChanged onto the UI thread for the convenience of developers.
If you want to disable this on an individual object, you can call ShouldAlwaysRaiseInpcOnUserInterfaceThread(false); for that object (this is a method call rather than a property as properties on ViewModel objects are generally reserved for INotifyPropertyChanged use)
If you want to disable this by default on all objects then you can use Mvx.Resolve<IMvxSettings>().AlwaysRaiseInpcOnUserInterfaceThread = false;
If during testing you want to provide a mock implementation for the UI thread marshalling, then see for example the N=29 video in http://mvvmcross.blogspot.co.uk/ - with some MockDispatcher code inside https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-29-TipCalcTest/TipCalcTest.Tests
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) });
}
}
}