Mvvmcross support NotifyPropertyChanged event where event args contain the old value? - c#

There is a UIHealthBar which is binded to a viewmodel property that is changed from 5 to 10. I would like to animate it with filled color from a old value (5) to new value (10). How can I do it in mvvmcross with a better approach ?

This sounds like it could be done with a pair of viewmodel properties - perhaps a tuple that is always changed together - e.g.
public class MyViewModel : MvxViewModel
{
public MyViewModel()
{
// subscribe for health updates here
}
public class HealthTuple
{
public double Old {get;set;}
public double New {get;set;}
}
private HealthTuple _health;
public HealthTuple Health
{
get { return _health; }
set { _health = value; RaisePropertyChanged(() => Health); }
}
private void OnNewHealth(HealthMessage message)
{
Health = new HealthTuple() { Old = _health.New, New = message.Value };
}
}
Your custom UIView - the UIHealthBar can then expose either a single property or two properties and you can bind these to the ViewModel's Health values. Drawing/animating the display is then 'normal UI kit work'

Related

How to notify Properties outside of an Object instance?

I am a WPF beginner and I have an issue with the PropertyChanged Events:
I have a viewmodel that contains an instance of another viewmodel. (I will use general names here)
I want the instance of the AxisVM to notify my SomeDouble property. (Can´t use a converter)
edit: I didn´t include the full classes here, the PropertyChangedEvent is obviously implemented.
public class ViewModel : INotifyPropertyChanged
{
private AxisVM axis;
public ViewModel()
{
this.AxisVM = new AxisVM();
}
public AxisVM Axis
{
get { return axis};
set { axis = value; FireOnPropertyChanged(); }
}
public double SomeDouble
{
get { return axis.Lowerlimit * 1.5 };
}
}
AxisVM also inherits from INotifyPropertyChanged (I use the ClassMemberName)
public class AxisVM: INotifyPropertyChanged
{
private double lowerLimit;
public double LowerLimit
{
get { return lowerLimit };
set { lowerLimit = value; FireOnPropertyChanged(); }
}
}
In XAML I bind the Viewmodel as a DataContext (where doesn´t matter in this case I think) and then bind the Lower limit to a textbox.
When I edit the textbox, the event of the lower limit of the axis gets fired and the value is changed (view and viewmodel) but I need to notify my SomeDouble property because it gets updated when the lower limit changes.
The property changed event of the axis instance in my ViewModel never gets fired even though I access a property of it (which does fire its event but doesn´t notify my SomeDouble property).
I am at loss right now, any help is appreciated.
Just handle the PropertyChanged of the AxisVM in your view model:
public class ViewModel : INotifyPropertyChanged
{
private readonly AxisVM axis;
public ViewModel()
{
axis = new AxisVM();
axis.PropertyChanged += Axis_PropertyChanged;
}
private void Axis_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
FireOnPropertyChanged(nameof(SomeDouble));
}
...
}
You could use an event for this.
Add an event in your AxisVM
public class AxisVM: INotifyPropertyChanged
{
public event EventHandler LowerLimitChanged;
private double lowerLimit;
public double LowerLimit
{
get { return lowerLimit };
set { lowerLimit = value; FireOnPropertyChanged(); LowerLimitChanged?.Invoke(this, EventArgs.Empty); }
}
}
And subscribe like this
public class ViewModel : INotifyPropertyChanged
{
private AxisVM axis;
public ViewModel()
{
this.AxisVM = new AxisVM();
this.AxisVM.LowerLimitChanged += OnLowerLimitChanged;
}
public AxisVM Axis
{
get { return axis};
set { axis = value; FireOnPropertyChanged(); }
}
public double SomeDouble
{
get { return axis.Lowerlimit * 1.5 };
}
public void OnLowerLimitChanged(object sender, EventArgs e)
{
FireOnPropertyChanged("SomeDouble");
}
}
You can remove FireOnPropertyChanged("SomeDouble"); in your property public AxisVM Axis because this will only be fired when the instance of AxisVM is set and not when a property in this instance has changed.
When you implemented INotifyPropertyChanged in AxisVM, you added PropertyChanged event there. Handle it in ViewModel and fire FireOnPropertyChanged().
Axis.PropertyChanged += OnAxisVMPropertyChanged(...)
void OnAxisVMPropertyChanged(..)
{
// Check property name
// Fire OnPropertyChanged for LowerLimit
}

ObservableCollection filtering on Windows Phone 8.1 Universal

I am writing windows phone 8.1 universal application and main applicaiton control is Pivot with few pivot items. In the pivot items are ListViews containing TestItems. I want to filter items on one list by IsRead property. Is it possible to just filter main collection without keeping 2 collections? CollectionViewSource does not support filtering a sorting on universal apps, if I know. But keeping (and synchronizing on changes) two collections doesn't look like good idea.
EDIT:
I have used ObservableCollection because list of items may be updated on the background. Probably it was not clear from original question.
class TestItem : ModelBase
{
private bool isRead;
public bool IsRead
{
get { return isRead; }
set
{
isRead = value;
NotifyPropertyChanged();
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
}
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
You can use Linq;
In your case:
using System.Linq;
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
//public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
public IEnumerable<TestItem> ItemsRead
{
get
{
IEnumerable<TestItem> itemsRead = from item in Items
where item.IsRead
select item;
return itemsRead;
}
}
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
Please, check syntax, it can contain some mistakes.
You can manipulate with the first collection, the second collection will be automatically updated.
You can define a CollectionViewSource in your XAML:
<Grid.Resources>
<CollectionViewSource x:Name="MyCollectionViewSource"/>
</Grid.Resources>
And then set it's source like this:
//Global variable
MainViewModel vm;
//Constructor
public MyPage(){
//Other code
vm = new MainViewModel();
vm.Items.CollectionChanged += Items_CollectionChanged;
UpdateViewSource();
}
private void Items_CollectionChanged(object sender, CollectionChangedEventArgs e){
UpdateViewSource();
}
private void UpdateViewSource(){
MyCollectionViewSource.Source = vm.Items.Where(x => x.IsRead);
}
I haven't tested this code.
You need only one ObservableCollection containing the initial objects and another property (let's say ItemsFiltered) with a get method returning the results after filtering. In the constructor you can subscribe to the CollectionChanged event of the observable collection to raise the OnPropertyChanged event for the ItemsFiltered property. You raise the same event when the filter state is changed. This is a simple example:
public MainViewModel()
{
_initialItems.CollectionChanged += (sender, e) => OnPropertyChanged("Items");
}
private ObservableCollection<TestItem> _initialItems = new ObservableCollection<TestItem>();
public List<TestItem> Items
{
get
{
if (IsReadFilter)
{
return _initialItems.Where(i => i.IsRead).ToList();
}
return _initialItems;
}
}
private bool _isReadFilter;
public bool IsReadFilter
{
get { return _isReadFilter; }
set
{
if (_isReadFilter != value)
{
_isReadFilter = value;
OnPropertyChanged("IsReadFilter");
OnPropertyChanged("Items");
}
}
}
Basically, the idea is that every time IsReadFilter value is changed, the UI gets notified that the Items property is changed and calls its get method to get the new value and update. Items are also updated every time the observable collection is changed from other places.

Two-way binding for UITextField.text not updating

When I change the content of an UITextField.Text "by hand", the change is not reflected to the viewmodel - the setter method is not called.
In my view model
private string _amount;
public string Amount
{
get { return _amount; }
set { _amount = value; RaisePropertyChanged(() => Amount); }
}
In my view
var _amount = new UITextField() { TranslatesAutoresizingMaskIntoConstraints = false };
View.AddSubview(_amount);
var set = this.CreateBindingSet<View, core.ViewModels.ViewModel>();
set.Bind(_amount).To(vm => vm.Amount);
set.Apply();
_amount.Text = "something";
Amount in the viewmodel is not updated, but if I type "something" into this textfield, then the viewmodel is updated.
I tried
_amount.WillChangeValue("Text");
_amount.Text = "something";
_amount.DidChangeValue("Text");
but that did'nt work.
How do can the view tell mvvmcross that the field is updated?
Edit: Solved by a custom binding listening on UITextField changes via an observer.
MvvmCross binds to Text using the delegate/event EditingChanged from objC - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Target/MvxUITextFieldTextTargetBinding.cs#L54 - this is why no event fires when you change the text.
One way around this could be to use an inherited control and a new property instead - e.g.
[Register("MyTextField")]
public class MyTextField : UITextField
{
public MyTextField() {
HookEvent();
}
public MyTextField(IntPtr ptr) {
HookEvent();
}
// other ctors as needed
private void HookEvent() {
EditingChanged += (s, e) => MyTextChanged.Raise(this);
}
public string MyText {
get { return Text; }
set { Text = value; MyTextChanged.Raise(this); }
}
public event EventHandler MyTextChanged;
}
This would allow you to use MyTextField in place of UITextField and MyText in place of Text

Bind "Enabled" properties of controls to a variable

I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.

How to watch a MediaPlayer and get feedback on it's CurrentProgress?

I'm writing an android app that uses the MediaPlayer. I've created a custom IAudioPlayer as a wrapper for the MediaPlayer so that I can eventually extend it to iOS.
public interface IAudioPlayer
{
bool IsPlaying { get; }
void Play(string fileName, int startingPoint);
void Play(string fileName);
void Pause();
void Stop();
int CurrentPosition();
bool HasFile();
void SkipForward(int seconds);
void SkipBackward(int seconds);
void SeekTo(int seconds);
}
Our app is structured using the Mvvm pattern and is using MvvmCross.
On our FragmentViewModel we've got commands such as IPlayCommand, IStopCommand, etc.
public class HomeFragmentViewModel : ViewModelBase
{
public HomeFragmentViewModel(IPlayCommand playCommand,
IStopCommand stopCommand,
ISkipForwardCommand skipForwardCommand,
ISkipBackwardCommand skipBackwardCommand)
{
_playCommand = playCommand;
_stopCommand = stopCommand;
_skipForwardCommand = skipForwardCommand;
_skipBackwardCommand = skipBackwardCommand;
}
private string _playPauseIcon = FontAwesome.icon_play;
public string PlayPauseIcon
{
get { return _playPauseIcon; }
set { _playPauseIcon = value; RaisePropertyChanged(() => PlayPauseIcon); }
}
private IPlayCommand _playCommand;
public IPlayCommand PlayCommand
{
get { return _playCommand; }
set { _playCommand = value; RaisePropertyChanged(() => PlayCommand); }
}
private IStopCommand _stopCommand;
public IStopCommand StopCommand
{
get { return _stopCommand; }
set { _stopCommand = value; RaisePropertyChanged(() => StopCommand); }
}
private ISkipForwardCommand _skipForwardCommand;
public ISkipForwardCommand SkipForwardCommand
{
get { return _skipForwardCommand; }
set { _skipForwardCommand = value; RaisePropertyChanged(() => SkipForwardCommand); }
}
private ISkipBackwardCommand _skipBackwardCommand;
public ISkipBackwardCommand SkipBackwardCommand
{
get { return _skipBackwardCommand; }
set { _skipBackwardCommand = value; RaisePropertyChanged(() => SkipBackwardCommand); }
}
}
On our View we've got buttons that bind to those commands, and all is working as expected.
However
Our view also has a SeekBar that we're going to use for quick scrubbing by the user. The seekbar needs to do two things...
allow the user quickly navigate to the spot that's required (this one "should" be easy
automatically update based on the track's current progress (this one I'm stuck on)
How can I write a notifier that triggers every second, and automatically update the seekbar binding? This notifier would need to live in the PCL with the Command Objects so that it can work cross platform. I'm struggling with getting started on this... I'm not sure where to create it or how to wire it up.
automatically update based on the track's current progress (this one I'm stuck on)
You should be able to do this using a binding to a View property. Here's some pseudo code:
In the ViewModel, add the SeekPosition property:
public double SeekPosition { /* normal INPC get/set */ }
In the View:
add a property and event pair like:
public event EventHandler CurrentPositionChanged;
public double CurrentPosition
{
get { return _mediaPlayer.CurrentPosition; }
set
{
_mediaPlayer.SeekTo(value);
}
}
add a timer to fire the CurrentPositionChanged event on the UI thread. For Android, create this timer in OnResume and destroy it in OnPause.
add a binding in OnCreate:
public override void OnCreate(args)
{
// normal base call and inflate
// ...
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(this).For(v => v.CurrentPosition).To(vm => vm.SeekPosition);
set.Apply();
}
Note that this approach doesn't use a timer in the PCL. This is because other platforms like iOS and Windows shouldn't need the timer - as they should be able to use progress callbacks/events from the media players on those platforms instead.

Categories