I want to ask you, how can I reflect changes on View, when I changed source. Here's my code. MODEL:
public class ExecutionMode
{
private List<DayItem> _dayItems = new List<DayItem>();
public enum TypeMode
{
None,
Once,
Daily,
Weekly
}
public TypeMode Mode { get; set; }
public DateTime? ExecutionTime { get; set; }
public DateTime? ExecutionDate { get; set; }
public List<DayItem> DayItems { get { return _dayItems; } set { _dayItems = value; } }
}
public class ProfileRecord
{
private ExecutionMode _mode = new ExecutionMode();
public ExecutionMode ExecutionMode { get { return _mode; } set { _mode = value; } }
}
ViewModel
public class NewProfileViewModel : INotifyPropertyChanged
{private ProfileRecord _record = new ProfileRecord();
public ProfileRecord Record { get{return _record; } set{_record=value; OnPropertyChanged("Record")}}
XAML:
<toolkit:TimePicker Header="Time of execution:" Margin="12,0,70,0" Value="{Binding ProfileRecord.ExecutionMode.ExecutionTime, Mode=TwoWay}" Visibility="{Binding ElementName=lpickerExecutionModes, Path=SelectedItem, Converter={StaticResource VisibilityConvert}, ConverterParameter=Once|Daily|Weekly}" />
When I set somewhere in code Record.ExecutionTime = Time it doesn't reflect on View. So I'm asking. Should I implement NotifyPropertyChanged in Model too?
Thanks
There are 2 ways of solving the problem:
Implement INotifyPropertyChanged on your Model class
Map all necessary properties from Model object as a properties within ViewModel and implement INotifyPropertyChanged on those properties.
I prefer the first approach, especially when model is really huge, has a lot of properties, and all these properties are used in View.
Related
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
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; }
}
}
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.
I have two tables named Process and ProcessTriggers. ProcessTriggers referring Process table.And need to create a screen to diplay Process as well as the ProcessTrigger related to the Process. I have created data objects using entity framework and my classes look like.
public partial class Process
{
public Process()
{
this.ProcessTriggers = new ObservableCollection<ProcessTrigger>();
}
public int ID { get; set; }
public int WorkflowID { get; set; }
public string Name { get; set; }
public bool IsBatch { get; set; }
public Nullable<System.DateTime> ModifiedOn { get; set; }
public virtual ObservableCollection<ProcessTrigger> ProcessTriggers { get; set; }
}
And my view model look like..
public class ProcessViewModel : ViewModel<Process>
{
private RelayCommand saveCommand;
private RelayCommand cancelCommand;
public ProcessViewModel()
{
using(var context = new PACEContext())
{
this.Entity = context.Processes.FirstOrDefault(i => i.ID == 1);
IsInEditMode = true;
}
}
}
I am binding these viewModel to my View, Entity properties are bound correctly, but the ProcessTriggerCollection is not binding to datagrid..
<DataGrid ItemsSource="{Binding Entity.ProcessTriggers}" AutoGenerateColumns="True">
</DataGrid>
I juts started learning WPF, MVVM and Entity framework.Can any one help.?
I understand the Process have a ObservableCollection of other class called ProcessTriggers.
Why you don't create a ObservableCollection and binding this?
private ObservableCollection<ProcessTrigger> _listProcessTriggers;
public ObservableCollection<ProcessTrigger> ListProcessTriggers
{
get { return _listProcessTriggers; }
set { _listProcessTriggers= value; RaisePropertyChanged("ListProcessTriggers"); }
}
public ProcessViewModel()
{
using(var context = new PACEContext())
{
this.Entity = context.Processes.FirstOrDefault(i => i.ID == 1);
ListProcessTriggers = Entity.ProcessTriggers;
IsInEditMode = true;
}
}
And In Xmal Binding
<DataGrid ItemsSource="{Binding ListProcessTriggers}" AutoGenerateColumns="True />
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.