ReactiveUI BindTo not updating the ReactiveList - c#

I have a ReactiveObject class named GroupedIssueTypeSearchFilter:
public class GroupedIssueTypeSearchFilter : ReactiveObject
{
public int CategoryID { get; set; }
public string CategoryTitle { get; set; }
int selectionCode;
public int SelectionCode
{
get { return selectionCode; }
set { this.RaiseAndSetIfChanged(ref selectionCode, value); }
}
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList { get; set; }
public GroupedIssueTypeSearchFilter(int catID, string catTitle, List<IssueTypeSearchFilter> issueTypeList)
{
this.CategoryID = catID;
this.CategoryTitle = catTitle;
this.IssueTypeFilterList = new ReactiveList<IssueTypeSearchFilter>(issueTypeList);
}
}
Then in IssueTypeFilterTableViewDelegate class, one of its properties is GroupedIssueTypesFilter:
public ReactiveList<GroupedIssueTypeSearchFilter> GroupedIssueTypesFilter { get; set; }
I also have a ViewController and its ViewModel. I used BindTo to bind one of the view model properties to that class:
IssueTypeFilterTableViewDelegate tvd = new IssueTypeFilterTableViewDelegate();
this.WhenAnyValue(vm => vm.ViewModel.GroupedStandardIssueTypesTV)
.BindTo(this, x => x.tvd.GroupedIssueTypesFilter);
GroupedStandardIssueTypesTV type is ReactiveList<GroupedIssueTypeSearchFilter>.
Later, with some operation inside the ViewModel, the GroupedStandardIssueTypesTV.IssueTypeFilterList value is changed.
But, GroupedIssueTypesFilter.IssueTypeFilterList value is not changed. I need to close the View, re-open it, then its value will be updated.
How to make the GroupedIssueTypesFilter follows the changes in GroupedStandardIssueTypesTV?

Fully agree with #ramonesteban78 answer.
But you can go in another way and refill your ReactiveList with Clear and Add/AddRange methods.
Like this:
public GroupedIssueTypeSearchFilter(int catID, string catTitle, List<IssueTypeSearchFilter> issueTypeList)
{
this.CategoryID = catID;
this.CategoryTitle = catTitle;
this.IssueTypeFilterList.Clear();
this.IssueTypeFilterList.AddRange(issueTypeList);
}
Don't forget read about 'Suppressing Notifications' here: https://docs.reactiveui.net/en/user-guide/lists/index.html

I think your problem is that you are not raising changes in your ReactiveList declaring it like:
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList { get; set; }
Try this:
ReactiveList<IssueTypeSearchFilter> issueTypeFilterList
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList
{
get { return issueTypeFilterList; };
set { this.RaiseAndSetIfChanged(ref issueTypeFilterList, value); };
}
Without the RaiseAndSetIfChanged you won`t be communicating changes to the view in your collection.
You can also subscribe to the observables that ReactiveList exposes like:
ItemsAdded
ItemsRemoved
ItemsMoved
Changed
etc
More info here: https://docs.reactiveui.net/en/user-guide/lists/index.html

Related

Binding C# BindingList<T> to a DataGridView

Maybe the question is a little bit dumb, but I did not quite find solution anywhere else.
So I am using a BindingList of custom made class objects as a DataSource for DataGridView.
Everything works fine with properties, that are directly inherited from other classes, but if I have an object of other class in the main class, its properties wont show up in DataGridView.
Classes are:
enum Valsts
{
Latvija,
Igaunija,
Ķīna,
ASV
}
class Razotajs
{
public Valsts valsts { get; set; }
public string razotajaNosaukums { get; set; }
}
class Tehnika
{
public string krasa { get; set; }
public Razotajs razotajs = new Razotajs();
}
class Viedierice : Tehnika
{
public string operetajsistema { get; set; }
public double ekranaIzmers { get; set; }
public bool irHDMI { get; set; }
}
class MobilaisTelefons : Viedierice
{
public string modelis { get; set; }
public double svars { get; set; }
public SimKarte sim = new SimKarte();
public override string ToString()
{
return String.Join(";", modelis.ToString(),svars.ToString(),sim.veids.ToString(),operetajsistema.ToString(),ekranaIzmers.ToString(),irHDMI.ToString(),krasa.ToString(),razotajs.razotajaNosaukums.ToString(),
sim.numurs.ToString(),razotajs.valsts.ToString());
}
}
class SimKarte
{
public string veids { get; set;}
public int numurs { get; set; }
}
For example- I can see columns "modelis" and "svars", but attributes like "veids" and "numurs" from class SimKarte are not included in the DataGridView.
Is there any solution for this?
I've tried to add { get; set; } after declaring a new instance of an object in the class, but it's not even a real thing. I really don't have any idea, what would help me to solve this.
Thank you all in advance! :)
Honestly, I think the simplest solution is the one JohnG proposed; add proxy properties to your main class that read/write the properties of the complex objects
A datagridview will show only the simple types it knows how to show, from the top level class. It will not dig into properties of properties (otherwise even adding a string column would cause the grid to fill up with a Length column an Isinterned column etc..)
partial class MobilaisTelefons : Viedierice
{
public string modelis { get; set; }
public double svars { get; set; }
public SimKarte sim { get; set; } = new SimKarte();
public override string ToString()
{
return String.Join(";",
modelis, svars, sim.veids, operetajsistema, ekranaIzmers, irHDMI, krasa, razotajs.razotajaNosaukums,
sim.numurs, razotajs.valsts);
}
}
partial class MobilaisTelefons {
public string SimVeids { get => sim.veids; set => sim.veids = value; }
public string SimNumers { get => sim.numers; set => sim.numers = value; }
public string RazotajsRazotajaNosaukums { get => razotajs.razotajaNosaukums; set => razotajs.razotajaNosaukums = value; }
public Valsts RazotajsValsts { get => razotajs.valsts; set => razotajs.valsts = value; }
}
Few tips:
I made the extension of the class partial so you can put it in another file. Hiding its members from intellisense would be hard work
the Enum column will probably show as an int. if you want it to be sensible, use a DataGridViewComboBox column bound to a list of all the enum values/names. On the column, set the DataMember to "RazotajsValsts", the DataSource to the list of enums, the DisplayMember to the property representing the enum name and the ValueMember to the property representing the enum value. See Enum.GetValues.
Enums should only have a plural name (if valsts is plural) if they are flags
classes should not have a plural name
public properties names should be in PascalCase not camelCase
I simplified your tostring: you don't need to call to string on everything; string join will do it. You especially don't need to call tostring on a string

Nesting list in model and the viewing and storing data

Sorry if this is a simple question but if I want to have a list inside a model, and later access and set the values of the list?
Say my main model looks like this:
public class StartPageModel : IPageViewModel<StartPage>
{
public IList<ListContent> ListContent { get; set; }
public StartPage CurrentPage { get; set; }
}
public class ListContent
{
public IList<ListElement> ArticleListContent { get; set; }
public IList<ListElement> InsightListContent { get; set; }
}
How can I set the ArticleListContent list to a value by referencing the parent model?
public ActionResult Index(StartPage currentPage)
{
var model = new StartPageModel(currentPage);
model.ListContent.ArticleListContent = GetListContent(currentPage.ArticleCollection);
}
However this returns the error:
IList does not contain a definition for 'ArticleListContent'
I'm not sure you require a collection of ListContent in your StartPageModel, correct me if I'm wrong.
Change
public IList<ListContent> ListContent { get; set; }
to
public ListContent ListContent { get; set; }
And provided ListContent is initialized, your assignment will work.
It's because it's referencing the List of ListContent, not an individual item in that list. Here's some examples:
var model = new StartPageModel(currentPage);
model.ListContent[0].ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access first in list
model.ListContent[1].ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access secondin list
model.ListContent.First().ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access first in list using Linq

Caliburn.Micro issue: PropertyChangedBase, BindableCollection and AutoMapper

I'm using Caliburn.Micro MVVM framework in my WPF application.
There is a DataGrid in a View that is binded to a BindableCollection<SomeObjectStatus>.
public class SomeObjectStatus
{
public string Message { get; set; }
public bool IsInitializing { get; set; }
}
I need to notify the UI somehow is any property of SomeObjectStatus is changed. The common way is to inherit PropertyChangedBase and Call NotifyOfPropertyChange() in each property setter:
public class SomeObjectStatus : PropertyChangedBase
{
private string _message;
private bool _isInitializing;
public string Message
{
get { return _message; }
set
{
if (value == _message)
return;
_message = value;
NotifyOfPropertyChange();
}
}
public bool IsInitializing
{
get { return _isInitializing; }
set
{
if (value == _isInitializing)
return;
_isInitializing = value;
NotifyOfPropertyChange();
}
}
}
But SomeObjectStatus is a Model class that I don't want to clog with a stuff like NotifyOfPropertyChange() etc.
I can create a clone class of SomeObjectStatus with same properties and implemented PropertyChangedBase specially for the ViewModel, lets call it SomeObjectStatusWithNotify. But in this case I need manually assign each property from SomeObjectStatus to SomeObjectStatusWithNotify when changes happen. In real project there are too much properties to assign them manually. So I need somehow to solve the question how to assign values from SomeObjectStatus to SomeObjectStatusWithNotify with same names automatically. It feels like I need AutoMapper-like functionality here. But I need to assign values to existing object instead creating a new one like AutoMapper does. Or maybe there is a elegant way to create PropertyChangedBase from regular class?
AutoMapper can assign values to an existing object if you use the correct overload of Map():
[TestClass]
public class C
{
[TestMethod]
public void M()
{
Mapper.CreateMap<SomeObject, SomeOtherObject>();
SomeObject source = new SomeObject {Name = "An Object"};
SomeOtherObject target = new SomeOtherObject {Id = 123};
Mapper.Map(source, target);
Assert.AreEqual(123, target.Id);
Assert.AreEqual("An Object", target.Name);
}
private class SomeOtherObject
{
public int Id { get; set; }
public string Name { get; set; }
}
private class SomeObject
{
public string Name { get; set; }
}
}

WPF DataGrid binding doesn't update

Here's my DataGrid:
<DataGrid x:Name="MoonMining"
ItemsSource="{Binding MarketData.MoonMinerals, ElementName=window}">
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
<DataGrid.Columns>
.. Yes i have columns and they are irrelevant to my question .
</DataGrid.Columns>
</DataGrid>
MarketData is a class which contains most of my programs logic. MoonMinerals is defined in that class:
public class MarketData
{
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
}
And here's my MoonMineral class:
[NotifyPropertyChanged]
public class MoonMineral
{
public MoonMineral()
: this("Def", "Def")
{
}
public MoonMineral(string name, string rarity)
{
Name = name;
Rarity = rarity;
}
public string Name { get; set; }
public double Price { get; set; }
public double Volume { get; set; }
public string Rarity { get; set; }
public double TransportVolume { get; set; }
public double TransportCosts { get; set; }
public double GrossProfit { get; set; }
public double NetProfit { get; set; }
}
As you can see, I'm using PostSharp to clear up my code, but when I manually implement INotifyPropertyChanged I have the same problem.
Now the problem is that my DataGrid doesn't update by itself, I have to manually call this in a method which modifies MoonMinerals:
var bindingExpression = MoonMining.GetBindingExpression(ItemsControl.ItemsSourceProperty);
if (bindingExpression != null)
bindingExpression.UpdateTarget();
I know this isn't big of a deal, but I wanted to finally manage to bind data to ui entirely using xaml. All my previous attempts involved setting DataGrids ItemsSource property every time I updated the data.
To sum up comments you're implementing INotifyPropertyChanged interface for MoonMineral class and use ObservableCollection which will handle changes to the collection but there seems to be nothing in place to handle changes to MoonMinerals property
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
You can either implement INotifyPropertyChanged interface in the class that exposes MoonMinerals property or change it to read-only and use only one instance of _moonMinerals and simply clear it and add/remove items
private readonly ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
}
Also, as a side note, you don't need
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
as this will set DataContext of the DataGrid to new instance of MoonMineral. It works in your case as you change binding context of ItemsSource using ElementName so DataContext is not used in your case.

Extend My Models

I am working on a WPF MVVM framework
I have a ItemType Model
public class ItemType
{
public long ItemTypeID { get; set; }
public long ItemCategoryID { get; set; }
public string Name { get; set; }
}
and other ItemCategory Model
public class ItemCategory
{
public long ItemCategoryID { get; set; }
public string Name { get; set; }
}
Now I want to Bind ItemType to a data grid, But I don't want to show ItemCategoryID. I want to show ItemCategory.Name
How can this be done without changing my original class?
This is what MVVM uses the ViewModel for.
Do not modify the Model but instead create ViewModel classes that are structured according to the needs of the View. that is where you would leave out the properties you don't want.
EDIT
Here is a way of doing that:
public class ItemTypeViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private string category;
public string Category
{
get { return category; }
set
{
if (category != value)
{
category = value;
OnPropertyChanged("Category");
}
}
}
public static ItemTypeViewModel FromModel(ItemType model)
{
var itemTypeViewModel =
new ItemTypeViewModel
{
Name = model.Name,
Category = categories[model.CategoryID].Name;
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ItemTypesViewModel : ObservableCollection<ItemTypesViewModel>
{
private ObservableCollection<ItemTypeViewModel> _collection;
public ObservableCollection<ItemTypeViewModel> Collection
{
get { return _collection; }
set
{
if (_collection != value)
{
_collection = value;
OnPropertyChanged(new PropertyChangedEventArgs("Collection"));
}
}
}
}
Use the static method to create instances of the ItemTypeViewModel for each ItemType in the model. Put them all in the 'Collection' property of the ItemTypeSViewModel.
Bind the DataGrid to the Collection.
I 'removed' the relation that exists in your model between ItemType and ItemCategory from my ViewModel. This is just one way of handling such a construction. Instead you could create a ViewModel class for the ItemCategory too and have a reference in the ItemTypeViewModel class to an instance of the ItemCategoryViewModel class.
Note that this is just one way of handling this. You could solve this problem in some other ways too. Also: you will need to provide a transformation from the ViewModel classes back to the Model as well.
A final bit of advise: if this is new to you start reading/watching tutorials on MVVM: https://stackoverflow.com/questions/2267903/learning-mvvm-for-wpf
The answer provided by both guys are perfectly valid and fit your needs, as much as I understood your question. The basic idea is:
#Amani:
//check if the query fits your needs
var temp = from i in categoryList
from it in itemList
where i.ItemCategoryID == it.ItemCategoryID
select new { i, it };
Make a cross query to generate a new composed type, which can be
public class ItemTypeViewModel
{
public long ItemTypeID { get; set; }
public string ItemCategoryName { get; set; }
public string ItemTypeName { get; set; }
}
this is according to MVVM patter guideline responded by #Erno. Use a collection of ItemTypeViewModel object to bind them to your view.
Hope this helps.
Regards.
If this is your database model you can easily make a view for join those tables. or you can use this following code as pattern :
var temp = from i in categoryList
from it in itemList
where i.ItemCategoryID == it.ItemCategoryID
select new { i, it };
Hope this help.

Categories