Detect when property changed at any element in the list - c#

I googled a lot and still not get an answer. The problem is very silly - I have collection of elements, and need to know when any property changed on any element. Not collection itself changed.
Pseudocode:
public class Item : ReactiveObject {
[ObservableAsProperty]
public bool PropertyToMonitor { get; }
}
public ReadOnlyObservableCollection<Item> Items;
So, how to get that any item in the Items list got PropertyToMonitor updated in observable manner?
Ugly workaround I use by now is:
Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
.Subscribe(_ => InvokeAsync(StateHasChanged))
Spend a day and gave up after all.
UPD: resolved with quite straight solution, pseudocode:
public class UnitTest3 : ReactiveObject
{
private readonly ITestOutputHelper _output;
public class Item : ReactiveObject
{
[Reactive]
public bool PropertyToMonitor { get; set; }
}
private ObservableCollection<Item> Items = new ObservableCollection<Item>();
public UnitTest3(ITestOutputHelper output)
{
this._output = output;
}
[Fact]
public void Test1()
{
bool signalled = false;
var item = new Item { };
Items.Add(new[] {item});
Items.Select(s => s.WhenPropertyChanged(p => p.PropertyToMonitor).Select(v => v.Value))
.Merge()
.Subscribe(v =>
{
_output.WriteLine("signalled: {0}", v);
signalled = v;
});
item.PropertyToMonitor = true;
signalled.Should().BeTrue();
}
}

Related

Getting Issue with Source Cache in ReactiveUI

I have one issue in subscribing to source cache. Let me describe the problem.
Lets say I have Test Class
public class Test {
public bool feature1 {get; set;} = false;
public bool feature2 {get; set; } = false;
public string name;
public Test(string name){
this.name = name
}
}
I want to see the changes happening in the property of test class and subscriber react according to change. But with current Implementation getting notification only when source is getting updated with new data not if any property of element in source cache is getting updated.
class Notifier {
public SourceCache<Test, string> testClassNotifier = new SourceCache<Test, string>(x => x.Name);
public Notifier(){
Task.Run(() =>
{
this.AddOrUpdateSourceCache();
this.SubscribeTestObj1();
this.SubscribeTestObj2();
}).ConfigureAwait(false);
}
private AddOrUpdateSourceCache()
{
List<Test> testListObj = new List<Test>() { new Test("test1"), new Test("test2") };
for (Test obj : testListObj) {
this.testClassNotifier.AddOrUpdate(obj);
}
Task.Run(async () => {
for(int i = 0; i<2; i++) {
this.testListObj[i].feature1 = true;
await Task.Delay(4000).ConfigureAwait(false);
// I want here to my get the notification in change with intial values as well.
}
}).ConfiguareAwait(false);
}
private IObservable<Test,string> GetNotification(string name){
// which api should use here ?? Or any way I can use `WhenAny` here.
return this.testClassNotifier.Watch(name);
}
private SubscribeTestObj1() {
this.GetNotification("test1").Subscribe(obj => // do something);
}
private SubscribeTestObj1() {
this.GetNotification("test2").Subscribe(obj => // do something);
}
}
One solution: implement INotifyPropertyChanged on the Test class, and use AutoRefresh()
Example:
public class Test : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
bool _feature1 = false;
public bool feature1
{
get => _feature1;
set
{
_feature1 = value;
PropertyChanged?.Invoke(this, new(nameof(feature1)));
}
}
// ... see the rest of the class in OP's question
}
Test:
var source = new SourceCache<Test, string>(x => x.name);
var a = new Test("a");
var b = new Test("b");
source
.Connect()
.AutoRefresh()
.Watch(b.name)
.Subscribe(change => Console.WriteLine($"Reason: <{change.Reason}> feature1: <{change.Current.feature1}>"));
source.AddOrUpdate(a);
source.AddOrUpdate(b);
b.feature1 = true;
Output:
Reason: <Add> feature1: <False>
Reason: <Refresh> feature1: <True>

MvvmCross - Passing a string with IMvxNavigationService

I'm currently working on a Xamarin.iOS project that uses a web-api to gather data. However, I'm running into some problems trying to pass the user input from a textfield to the Tableview that gets the result from the api.
To do this I've followed the example on the MvvmCross documentation.
The problem is that the input from the Textfield never reaches the 'Filter' property in my TableviewController's viewmodel. I think I'm not passing the string object correctly to my IMvxNavigationService when called.
To clarify, in my UserinputViewController I'm binding the textfield's text like so:
[MvxFromStoryboard(StoryboardName = "Main")]
public partial class SearchEventView : MvxViewController
{
public SearchEventView (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel> set = new MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel>(this);
set.Bind(btnSearch).To(vm => vm.SearchEventCommand);
set.Bind(txtSearchFilter).For(s => s.Text).To(vm => vm.SearchFilter);
set.Apply();
}
}
The Viewmodel linked to this ViewController looks like this:
public class SearchEventViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
private string _searchFilter;
public string SearchFilter
{
get { return _searchFilter; }
set { _searchFilter = value; RaisePropertyChanged(() => SearchFilter); }
}
public SearchEventViewModel(IMvxNavigationService mvxNavigationService)
{
this._navigationService = mvxNavigationService;
}
public IMvxCommand SearchEventCommand {
get {
return new MvxCommand<string>(SearchEvent);
}
}
private async void SearchEvent(string filter)
{
await _navigationService.Navigate<EventListViewModel, string>(filter);
}
}
And finally, TableviewController's viewmodel looks like this:
public class EventListViewModel : MvxViewModel<string>
{
private readonly ITicketMasterService _ticketMasterService;
private readonly IMvxNavigationService _navigationService;
private List<Event> _events;
public List<Event> Events
{
get { return _events; }
set { _events = value; RaisePropertyChanged(() => Events); }
}
private string _filter;
public string Filter
{
get { return _filter; }
set { _filter = value; RaisePropertyChanged(() => Filter); }
}
public EventListViewModel(ITicketMasterService ticketMasterService, IMvxNavigationService mvxNavigationService)
{
this._ticketMasterService = ticketMasterService;
this._navigationService = mvxNavigationService;
}
public IMvxCommand EventDetailCommand {
get {
return new MvxCommand<Event>(EventDetail);
}
}
private void EventDetail(Event detailEvent)
{
_navigationService.Navigate<EventDetailViewModel, Event>(detailEvent);
}
public override void Prepare(string parameter)
{
this.Filter = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
//Do heavy work and data loading here
this.Events = await _ticketMasterService.GetEvents(Filter);
}
}
Whenever trying to run, the string object 'parameter' in my TableviewController's Prepare function remains 'null' and I have no idea how to fix it. Any help is greatly appreciated!
I believe the issue is with your command setup
new MvxCommand<string>(SearchEvent);
As this command is being bound to a standard UIButton. It will not pass through a parameter value of your filter but null instead. So the string parameter generic can be removed. Additionally, as you want to execute an asynchronous method I would suggest rather using MvxAsyncCommand
new MvxAsyncCommand(SearchEvent);
Then in terms of SearchEvent method you can remove the parameter. The value of filter is bound to your SearchFilter property. It is this property's value that you want to send as the navigation parameter.
private async Task SearchEvent()
{
await _navigationService.Navigate<EventListViewModel, string>(SearchFilter);
}

Equivalence in ReactiveUI?

I have this property inside a ReactiveObject:
bool IsValid => Children.All(child => child.IsValid);
The problem is that, of course, it doesn't raise any change notification when children are modified (their "IsValid" property).
How is this done the right way in ReactiveUI?
NOTE:
Child is a ReactiveObject, too.
I can modify both classes, parent
and children, to meet RxUI precepts and guidelines with no
restriction.
ObservableAsPropertyHelper< bool > is what you need, if your Children property is a reactive list you can merge Changed and ItemChanged observables and have something like:
public class MyViewModel : ReactiveObject
{
private readonly ObservableAsPropertyHelper<bool> _isValidPropertyHelper;
public MyViewModel()
{
var listChanged = Children.Changed.Select(_ => Unit.Default);
var childrenChanged = Children.ItemChanged.Select(_ => Unit.Default);
_isValidPropertyHelper = listChanged.Merge(childrenChanged)
.Select(_ => Children.All(c => c.IsValid))
.ToProperty(this, model => model.IsValid);
}
public bool IsValid
{
get { return _isValidPropertyHelper.Value; }
}
public ReactiveList<Item> Children { get; set; }
}

Wpf ICollectionView Binding item cannot resolve property of type object

I have bound a GridView with an ICollectionView in the XAML designer the properties are not known because the entity in the CollectionView have been transformed into type Object and the entity properties can't be accessed, it runs fine no error but the designer shows it as an error, if I bind to the collection I can access the properties fine
Example the entity is a Person with a string Name property I place them in an ObservableCollection<Person> and get the view from it and bind it to the GridView.ItemsSource now when I try to set the column header DataMemberBinding.FirstName property the designer shows it as an error
Cannot Resolve property 'FirstName' in data Context of type object
Is it a bug or is it Resharper playing tricks on me
Sample code:
public class Person
{
public string FirstName{
get { return _firstName; }
set { SetPropertyValue("FirstName", ref _firstName, value); }
}
}
public class DataService
{
public IDataSource DataContext { get; set; }
public ICollectionView PersonCollection{ get; set; }
public DataService()
{
DataContext = new DataSource();
//QueryableCollectionView is from Telerik
//but if i use any other CollectionView same thing
//DataContext Persons is an ObservableCollection<Person> Persons
PersonCollection = new QueryableCollectionView(DataContext.Persons);
}
}
<telerik:RadGridView x:Name="ParentGrid"
ItemsSource="{Binding DataService.PersonCollection}"
AutoGenerateColumns="False">
<telerik:RadGridView.Columns >
<telerik:GridViewDataColumn Header="{lex:Loc Key=FirstName}"
DataMemberBinding="{Binding FirstName}"/>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
The warnings that Resharper is giving you in the XAML view is because the design-time view of the control does not know what type it's data-context is. You can use a d:DesignInstance to help with your bindings.
Add the following (replacing Assembly/Namespace/Binding Target names appropriately)
<UserControl x:Class="MyNamespace.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup‐compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lcl="clr‐namespace:MyAssembly"
d:DataContext="{d:DesignInstance Type=lcl:ViewModel}">
Your entity has not been transformed in object, it's because the interface ICollectionView is not a generic collection so ReSharper has no way to know that it holds a collection of Person.
You can create a generic version of ICollectionView and use it for your PersonCollection property as demonstrated in this post https://benoitpatra.com/2014/10/12/a-generic-version-of-icollectionview-used-in-a-mvvm-searchable-list/.
First some interfaces:
public interface ICollectionView<T> : IEnumerable<T>, ICollectionView
{
}
public interface IEditableCollectionView<T> : IEditableCollectionView
{
}
The implementation:
public class GenericCollectionView<T> : ICollectionView<T>, IEditableCollectionView<T>
{
readonly ListCollectionView collectionView;
public CultureInfo Culture
{
get => collectionView.Culture;
set => collectionView.Culture = value;
}
public IEnumerable SourceCollection => collectionView.SourceCollection;
public Predicate<object> Filter
{
get => collectionView.Filter;
set => collectionView.Filter = value;
}
public bool CanFilter => collectionView.CanFilter;
public SortDescriptionCollection SortDescriptions => collectionView.SortDescriptions;
public bool CanSort => collectionView.CanSort;
public bool CanGroup => collectionView.CanGroup;
public ObservableCollection<GroupDescription> GroupDescriptions => collectionView.GroupDescriptions;
public ReadOnlyObservableCollection<object> Groups => collectionView.Groups;
public bool IsEmpty => collectionView.IsEmpty;
public object CurrentItem => collectionView.CurrentItem;
public int CurrentPosition => collectionView.CurrentPosition;
public bool IsCurrentAfterLast => collectionView.IsCurrentAfterLast;
public bool IsCurrentBeforeFirst => collectionView.IsCurrentBeforeFirst;
public NewItemPlaceholderPosition NewItemPlaceholderPosition
{
get => collectionView.NewItemPlaceholderPosition;
set => collectionView.NewItemPlaceholderPosition = value;
}
public bool CanAddNew => collectionView.CanAddNew;
public bool IsAddingNew => collectionView.IsAddingNew;
public object CurrentAddItem => collectionView.CurrentAddItem;
public bool CanRemove => collectionView.CanRemove;
public bool CanCancelEdit => collectionView.CanCancelEdit;
public bool IsEditingItem => collectionView.IsEditingItem;
public object CurrentEditItem => collectionView.CurrentEditItem;
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => ((ICollectionView) collectionView).CollectionChanged += value;
remove => ((ICollectionView) collectionView).CollectionChanged -= value;
}
public event CurrentChangingEventHandler CurrentChanging
{
add => ((ICollectionView) collectionView).CurrentChanging += value;
remove => ((ICollectionView) collectionView).CurrentChanging -= value;
}
public event EventHandler CurrentChanged
{
add => ((ICollectionView) collectionView).CurrentChanged += value;
remove => ((ICollectionView) collectionView).CurrentChanged -= value;
}
public GenericCollectionView([NotNull] ListCollectionView collectionView)
{
this.collectionView = collectionView ?? throw new ArgumentNullException(nameof(collectionView));
}
public IEnumerator<T> GetEnumerator()
{
return (IEnumerator<T>) ((ICollectionView) collectionView).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollectionView) collectionView).GetEnumerator();
}
public bool Contains(object item)
{
return collectionView.Contains(item);
}
public void Refresh()
{
collectionView.Refresh();
}
public IDisposable DeferRefresh()
{
return collectionView.DeferRefresh();
}
public bool MoveCurrentToFirst()
{
return collectionView.MoveCurrentToFirst();
}
public bool MoveCurrentToLast()
{
return collectionView.MoveCurrentToLast();
}
public bool MoveCurrentToNext()
{
return collectionView.MoveCurrentToNext();
}
public bool MoveCurrentToPrevious()
{
return collectionView.MoveCurrentToPrevious();
}
public bool MoveCurrentTo(object item)
{
return collectionView.MoveCurrentTo(item);
}
public bool MoveCurrentToPosition(int position)
{
return collectionView.MoveCurrentToPosition(position);
}
public object AddNew()
{
return collectionView.AddNew();
}
public void CommitNew()
{
collectionView.CommitNew();
}
public void CancelNew()
{
collectionView.CancelNew();
}
public void RemoveAt(int index)
{
collectionView.RemoveAt(index);
}
public void Remove(object item)
{
collectionView.Remove(item);
}
public void EditItem(object item)
{
collectionView.EditItem(item);
}
public void CommitEdit()
{
collectionView.CommitEdit();
}
public void CancelEdit()
{
collectionView.CancelEdit();
}
}
And finally the usage:
ICollectionView<Person> PersonCollectionView { get; }
In the constructor:
var view = (ListCollectionView) CollectionViewSource.GetDefaultView(PersonCollection);
PersonCollectionView = new GenericCollectionView<Person>(view);
Neither
d:DataContext="{d:DesignInstance Type=lcl:ViewModel}">
nor
GenericCollectionView
works directly for a DataGrid with a CollectionViewSource.
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding collectionViewSource.View}"
SelectedItem="{Binding SelectedRow}"
We can't set "d:DataContext"; because, we often need to bind multiple properties to our viewmodel.
The CollectionViewSource creates new ListCollectionView which is runtime instantiated each time you set the Source property. Since setting the Source property is the only reasonable way to refresh a range of rows, we can't keep a GenericCollectionView around.
My solution is perhaps perfectly obvious, but I dumped the CollectionViewSource. By making creating a property
private ObservableCollection<ListingGridRow> _rowDataStoreAsList;
public GenericCollectionView<ListingGridRow> TypedCollectionView
{
get => _typedCollectionView;
set { _typedCollectionView = value; OnPropertyChanged();}
}
public void FullRefresh()
{
var listData = _model.FetchListingGridRows(onlyListingId: -1);
_rowDataStoreAsList = new ObservableCollection<ListingGridRow>(listData);
var oldView = TypedCollectionView;
var saveSortDescriptions = oldView.SortDescriptions.ToArray();
var saveFilter = oldView.Filter;
TypedCollectionView = new GenericCollectionView<ListingGridRow>(new ListCollectionView(_rowDataStoreAsList));
var newView = TypedCollectionView;
foreach (var sortDescription in saveSortDescriptions)
{
newView.SortDescriptions.Add(new SortDescription()
{
Direction = sortDescription.Direction,
PropertyName = sortDescription.PropertyName
});
}
newView.Filter = saveFilter;
}
internal void EditItem(object arg)
{
var view = TypedCollectionView;
var saveCurrentPosition = view.CurrentPosition;
var originalRow = view.TypedCurrentItem;
if (originalRow == null)
return;
var listingId = originalRow.ListingId;
var rawListIndex = _rowDataStoreAsList.IndexOf(originalRow);
// ... ShowDialog ... DialogResult ...
var lstData = _model.FetchListingGridRows(listingId);
_rowDataStoreAsList[rawListIndex] = lstData[0];
view.MoveCurrentToPosition(saveCurrentPosition);
view.Refresh();
}
After adding
public T TypedCurrentItem => (T)collectionView.CurrentItem;
To the GenericCollectionView provided by Maxence.

How to use System.Lazy with Setter to Lazy Initialization of List in POCO Entities?

I want to use System.Lazy to Lazy Initialization of my List in my Entites:
public class Questionary
{
private Lazy<List<Question>> _questions = new Lazy<List<Question>>(() => new List<Question>());
public IList<Question> Questions { get { return _questions.Value; } set { _questions.Value = value; } }
}
The problem is on my SETTER, get this error: The property 'System.Lazy<T>.Value' has no setter
If i want to do MyInstance.Questions = new List<Question> { ... } ?
How do I proceed?
Update:
I'm trying to avoid that:
private IList<Question> _questions;
//Trying to avoid that ugly if in my getter:
public IList<Question> Questions { get { return _questions == null ? new List<Question>() : _questions; } set { _questions = value } }
I'm doing something wrong?
You could do something like this:
public class Questionary
{
private Lazy<IList<Question>> _questions =
new Lazy<IList<Question>>(() => new List<Question>());
public IList<Question> Questions
{
get { return _questions.Value; }
set { _questions = new Lazy<IList<Question>>(() => value); }
}
}
However, I don't see why you need Lazy<T> here at all. There is no benefit in using it, because the initialization of a new List<T> should be the same as the initialization of a new Lazy<T>...
I think it would be enough to keep it as simple as this:
public class Questionary
{
private IList<Question> _questions = new List<Question>();
public IList<Question> Questions
{
get { return _questions; }
set { _questions = value; }
}
}
or
public class Questionary
{
public Questionary()
{
Questions = new List<Question>();
}
public IList<Question> Questions { get; set; }
}
It's not clear what you're trying to do. You can't set the value of a Lazy<T> - it's as simple as that. You can only ask it for a value, and it will execute the delegate you've provided the first time the values are requested.
Do you really need a setter in your class at all? Perhaps you just want:
public class Questionary
{
private Lazy<List<Question>> _questions =
new Lazy<List<Question>>(() => new List<Question>());
public IList<Question> Questions { get { return _questions.Value; } }
}
Non-lazy solution - same benefit in terms of being initialized only when called:
private List<Question> _questions;
public List<Question> Questions { get { return _questions ?? (_questions = new List<Question>()); } set { _questions = value; } }

Categories

Resources