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; }
}
Related
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();
}
}
I have a collection of classes contained in a ObservibaleCollection<MyObj> and MyObj implements INotifyPropertyChanged, but I need a property located outside of it that references a property in the collection via linq and creates its own collection to update on both the collection change and any of its content linq bound properties changing.
For sake of argument and simplicity lets say my class MyObj contains a property called IsVisible. I want a property that implements its own INotifyPropertyChanged to get a list of MyObj where IsVisible == true and keep it up to date regardless id the collection of MyObj changes or the IsVisible property does.
Is this possible without attaching to the collection changed event and subsequently just directly attaching to each child MyObj.IsVisible property? Is there a way to get INotify to bubble up through linq?
public class MyObj:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsVisible
{
get { return _IsVisible; }
protected set { if (value != _IsVisible) { _IsVisible= value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsVisible")); } }
}
private bool _IsVisible;
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
ObservableCollection<MyObj> myVisibleObjs {
get{
return myObjs.where(o => o.IsVisible);
}
}
}
I hope what I'm asking makes sense.
You could make use of Reactive Extensions, but for this specific requirement - maintaining a myVisibleObjs - I would use the dynamic data lib.
Try out the following:
static void Main(string[] args)
{
Foo f = new Foo();
f.BindToVisibleObjects();
// add more dummy data
f.Add(false);
f.Add(true);
// There will be 2 visible objects in MyVisibleObjects
foreach (var d in f.MyVisibleObjects)
{
Console.WriteLine(d.IsVisible);
}
Console.ReadKey();
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
public ReadOnlyObservableCollection<MyObj> MyVisibleObjects;
public Foo()
{
// add some dummy data
myObjs.Add(new MyObj() { IsVisible = true });
myObjs.Add(new MyObj() { IsVisible = false });
}
public void BindToVisibleObjects()
{
myObjs.ToObservableChangeSet()
.Filter(o => o.IsVisible)
.Bind(out MyVisibleObjects)
.DisposeMany()
.Subscribe();
}
public void Add(bool vis)
{
myObjs.Add(new MyObj() { IsVisible = vis });
}
}
The key here is that we bind the filtered observable changeset to a new collection that will be updated as your myObjs changes.
I am using the following mapping to map my data object to viewmodel object.
ObjectMapper.cs
public static class ObjectMapper
{
public static void Configure()
{
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Title,
opt => opt.ResolveUsing<TitleValueResolver>())
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<NameValueResolver >())
.ForMember(dest => dest.ShortName,
opt => opt.ResolveUsing<ShortNameValueResolver >());
}
}
Parser
public class Parser{
public string GetTitle(string title){
/* add some logic */
return title;
}
public string GetName(string title){
/* add some logic */
return name;
}
public string GetShortName(string title){
/* add some logic */
return shortname;
}
}
AutoMapperCustomResolvers.cs
public class TitleValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public TitleValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetTitle(source.TITLE);
}
}
public class NameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public NameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetName(source.TITLE);
}
}
public class ShortNameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public ShortNameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetShortName(source.TITLE);
}
}
I am using the above code to add logic to the destination property using the separate custom value resolvers. Not sure is this the right approach.
i) Is there a better way to achieve this?
ii) And how to use unity to resolve in case i want to inject some dependency to custom resolver constructor?
Thanks
As I understand your question, you want to utilize a ValueResolver, that resolves multiple source properties into an intermediate data object, which is used to resolve multiple target properties. As an example, I assume the following source, target, intermediate and resolver types:
// source
class User
{
public string UserTitle { get; set; }
}
// target
class UserViewModel
{
public string VM_Title { get; set; }
public string VM_OtherValue { get; set; }
}
// intermediate from ValueResolver
class UserTitleParserResult
{
public string TransferTitle { get; set; }
}
class TypeValueResolver : ValueResolver<User, UserTitleParserResult>
{
protected override UserTitleParserResult ResolveCore(User source)
{
return new UserTitleParserResult { TransferTitle = source.UserTitle };
}
}
You need a target property in order to utilize opt.ResolveUsing<TypeValueResolver>(). This means, you can establish a mapping, where an appropriate target property is available.
So, for the moment, lets wrap the result into an appropriate container type:
class Container<TType>
{
public TType Value { get; set; }
}
And create a mapping
Mapper.CreateMap<User, Container<UserViewModel>>()
.ForMember(d => d.Value, c => c.ResolveUsing<TypeValueResolver>());
And another mapping
Mapper.CreateMap<UserTitleParserResult, UserViewModel>()
.ForMember(d => d.VM_Title, c => c.MapFrom(s => s.TransferTitle))
.ForMember(d => d.VM_OtherValue, c => c.Ignore());
And another mapping
Mapper.CreateMap<User, UserViewModel>()
.BeforeMap((s, d) =>
{
Mapper.Map<User, Container<UserViewModel>>(s, new Container<UserViewModel> { Value = d });
})
.ForAllMembers(c => c.Ignore());
// establish more rules for properties...
The last mapping is a bit special, since it relies on a nested mapping in order to update the destination with values from source via separately configured mapping rules. You can have multiple different transfer mappings for different properties by adding appropriate intermediate mappings and calls in BeforeMap of the actual mapped type. The properties that are handled in other mappings need to be ignored, since AutoMapper doesn't know about the mapping in BeforeMap
Small usage example:
var user = new User() { UserTitle = "User 1" };
// create by mapping
UserViewModel vm1 = Mapper.Map<UserViewModel>(user);
UserViewModel vm2 = new UserViewModel() { VM_Title = "Title 2", VM_OtherValue = "Value 2" };
// map source properties into existing target
Mapper.Map(user, vm2);
Dunno if this helps you. There might be better ways if you rephrase your question to describe your initial problem instead of what you suspect to be a solution.
I'm using ReactiveUI and its Router.
Some of my IRoutableViewModels implements a interface IHaveCommands, which have a property IEnumerable<IReactiveCommand> Commands { get; }
So, what I want, is a list of commands for the current viewmodel in my IScreen implementation, the AppBootstrapper.
What would be 'the right' way of implementing this?
I have sort of got it to work with the following code, but my guts tells me that there are other, better ways of doing it....
public class AppBootstrapper : ReactiveObject, IScreen
{
public IRoutingState Router { get; private set; }
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, IRoutingState testRouter = null)
{
Router = testRouter ?? new RoutingState();
Router.CurrentViewModel.Where(x => x != null)
.Select(x => typeof(IHaveCommands).IsAssignableFrom(x.GetType()) ? ((IHaveCommands)x).Commands : null)
.ToProperty(this, x => x.Commands, out _toolbarCommands);
...
}
readonly ObservableAsPropertyHelper<IEnumerable<CommandSpec>> _toolbarCommands;
public IEnumerable<CommandSpec> ToolbarCommands { get { return _toolbarCommands.Value; } }
}
Any tips?
This looks great to me! Other than some possible minor readability fixups, this is What You Should Do™. Here's a slightly cleaner version:
Router.CurrentViewModel
.Select(x => {
var haveCmds = x as IHaveCommands;
return haveCmds != null ? haveCmds.Commands : null;
})
.ToProperty(this, x => x.Commands, out _toolbarCommands);
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.