MVVM: how to create a ViewModel from Model object - wpf

I want to get values of my model and create a viewmode
In my Model I have
public class TestElement
{
public TestElement CurrentNode { get; set; }
public TestElement Parent { get; set; }
}
I have some method that do this
if (thisNode == null)
{
thisNode = new TestElement { Name = name, Parent = CurrentNode };
currentCollection.Add(thisNode);
}
In my view model I want to create TestElementViewModel Parent and get my model Parent values
public class TestElementViewModel
{
public TestElementViewModel Parent { get; set; }
I want to use it in this method
public IEnumerable<TestElementViewModel> ToTreeViewModel(IEnumerable<TestElement> treemodel)
{
foreach (TestElementitem in treemodel)
yield return new TestElementViewModel
{
Id = item.Id,
Name = item.Name,
Children = ToTreeViewModel(item.Children).ToList(),
Parent = item.Parent
};
}
}
How can I achieve that?

I'm guessing your casting error occurs on the the line
Parent = item.Parent
Well the Parent property in your TestElementViewModel isn't a TestElement type so you can't do that.
Try assigning a new TestElementViewModel instead.
Parent = new TestElementViewModel { Id = item.Parent.Id, Name = item.Parent.Name, ... }
One improvement you might want to consider is using wrappers in your ViewModel class, which will make assigning properties a little easier.
For example,
public class TestElementViewModel : INotifyPropertyChanged
{
public TestElementViewModel(TestElement model)
{
Model = model;
if(Model.Parent != null)
Parent = new TestElementViewModel(Model.Parent);
}
public TestElement Model { get; private set; }
private TestElementViewModel _parent;
public TestElementViewModel Parent
{ get { return _parent; }
set { _parent = value; OnPropertyChanged("Parent"); }
}
public int Id
{
get { return Model.Id; }
set { Model.Id = value; OnPropertyChanged("Id"); }
}
// rest of the properties need wrapping too
}
makes it so that you don't have to manually assign the properties each time you instantiate a new viewmodel.

Related

Caliburn.Micro: moving between pages with Conductor: how can I call a parent class method or property from a child class?

This is my main, parent class, ViewModel:
[AutofacRegisterType(PAGE_NAME, typeof(IMainPage), IsSingleInstance = false)]
public class MainPageViewModel : MainPageViewModelBase
{
public const string PAGE_NAME = "MainPage";
public MainPageChildsConductor ChildPages { get; private set; }
public IMainPageChild ActiveChildPage
{
get { return ChildPages.ActiveItem; }
}
public MainPageViewModel()
{
PageName = PAGE_NAME;
DisplayName = PAGE_NAME;
DisposeOnDeactivate = true;
InitChildPages();
}
private void InitChildPages()
{
ChildPages = new MainPageChildsConductor();
ChildPages.Parent = this;
ChildPages.ConductWith(this);
var trallchilds = TypeRegistry.GetItemsByType<IMainPageChild>();
var trchilds = trallchilds.Where(p => p.AutoRegister != null && p.AutoRegister.Name.StartsWith(PAGE_NAME + ":")).ToList();
var childs = new List<IMainPageChild>();
foreach (var trchild in trchilds)
{
var child = trchild.CreateType<IMainPageChild>();
childs.Add(child);
}
childs.Sort((a, b) => a.PageIndex.CompareTo(b.PageIndex));
ChildPages.Items.AddRange(childs);
ChildPages.ActivateWith(this);
ChildPages.DeactivateWith(this);
}
}
This is one of my child classes, ViewModel:
[AutofacRegisterType(PAGE_NAME, typeof(IMainPageChild), IsSingleInstance = false)]
public class Child1PageViewModel : MainPageChildViewModelBase
{
public const string PAGE_NAME = "ChildPage:Child1Page";
public const int PAGE_INDEX = 30;
public Child1PageViewModel()
{
PageName = PAGE_NAME;
DisplayName = "Child1";
PageIndex = PAGE_INDEX;
InitButtons();
InitSummaryData();
}
}
And this is the class that inherits the Caliburn.Micro class Conductor:
public class MainPageChildsConductor : Conductor<IMainPageChild>.Collection.OneActive
{
public MainPageChildsConductor()
{
}
public override void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
base.NotifyOfPropertyChange(propertyName);
if (Parent is INotifyPropertyChangedEx)
((INotifyPropertyChangedEx)Parent).Refresh();
}
}
The question is: how can I call a method or property that exists in the parent page 'MainPageViewModel' from the child page 'Child1PageViewModel'???
Your child view needs to inherit from Screen and when activated in the parent view model, you obtain a reference to the child VM's Parent property via inheritance from Screen.
See this page in the documentation for more details:
Screens, Conductors and Composition.
This is how I do it in one of my projects:
public class MainViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<CreateNewGraphEvent>, IHandle<AddMeasurementsToGraphEvent>, IHandle<DeleteNamedGraphEvent>,
IHandle<GraphRenamedEvent>, IHandle<AddDuplicateGraphEvent>
{
private readonly TreeListViewModel _TreeView;
private readonly StatusBarViewModel _StatusBar;
private readonly IEventAggregator _Aggregator;
private readonly ProgressDialogViewModel _Progress;
public MainViewModel(IEventAggregator aggregator, TreeListViewModel treeView, StatusBarViewModel statusBar)
{
if (aggregator == null)
throw new ArgumentNullException("aggregator");
_Aggregator = aggregator;
_Aggregator.Subscribe(this);
if (statusBar == null)
throw new ArgumentNullException("statusBar");
_StatusBar = statusBar;
if (treeView == null)
throw new ArgumentNullException("treeView");
_TreeView = treeView;
this.Items.CollectionChanged += Items_CollectionChanged;
}
public void Handle(CreateNewGraphEvent message)
{
ChartViewModel document = IoC.Get<ChartViewModel>(message.SelectedGraphType.ToString());
if (document == null) return;
document.DisplayName = message.GraphName;
document.CloseAction = this.CloseAction;
document.SelectedGraphType = message.SelectedGraphType;
ActivateItem(document);
}
}
public class ChartViewModel : Screen, IHandle<MeasurementRenamedEvent>
{
private readonly IEventAggregator _Aggregator;
private readonly ISupportServices _Services;
public ChartViewModel(IEventAggregator aggregator, ISupportServices services) : base(aggregator, services)
{
if (aggregator == null)
throw new ArgumentNullException("aggregator");
_Aggregator = aggregator;
_Aggregator.Subscribe(this);
if (services == null)
throw new ArgumentNullException("services");
_Services = services;
}}
When ActivateItem item method is called in MainViewModel, the CihldViewModel is added to the Items collection in MainViewModel and activates the child VM, where you can then access MainViewModel through the this.Parent property in ChildViewModel.

Access treeviewitem from HierarchicalDataTemplate in WPF

I am trying to access the treeviewitem created using HierarchicalDataTemplate based on the Name of the header. Also i want to access the control inside (in this case rectangle) the treeviewitem and change its color. I tried many ways but no success. Below is my code. I am generating Treeview using custom class and xml.
public partial class Window1 : Window
{
ObservableCollection<Step> TreeViewTemplate;
public Window1()
{
TreeViewTemplate = new ObservableCollection<Step>();
InitializeComponent();
SetDataTemplate("NEWSITECOPPER_PROPOSAL", "Proposal");
tvMain.ItemsSource = TreeViewTemplate;
getTreeViewItem();
}
private void getTreeViewItem()
{
TreeViewItem item = (TreeViewItem)(tvMain.ItemContainerGenerator.ContainerFromItem(tvMain.Items[3]));
}
private void SetDataTemplate(string ProcessName, string journeyName)
{
try
{
TreeViewTemplate.Clear();
//XDocument xDoc = XDocument.Load(#"C:\Users\606347769\Desktop\Hemil\Others\TreeView\TreeView\Data.xml");
XDocument xDoc = XDocument.Load(#"C:\Users\606347769\Documents\Visual Studio 2008\Projects\TestAPplication\WpfApplication1\ProcessJourneyCriteria.xml");
var JourneySteps = xDoc.Elements("ProcessAreas").Elements("Process").Where(x =>
x.Attribute("name").Value == ProcessName).Select(y =>
y.Elements("Journey").Where(k => k.Attribute("name").Value == journeyName));
var FinalSteps = JourneySteps.FirstOrDefault();
FinalSteps.Elements("Step").ToList<XElement>().ForEach(x =>
{
string key = x.Attribute("name").Value;
ObservableCollection<ChildStep> value = new ObservableCollection<ChildStep>();
x.Elements("ChildStep").ToList<XElement>().ForEach(y =>
{
ObservableCollection<GrandChildStep> GC = new ObservableCollection<GrandChildStep>();
y.Elements("GrandChildStep").ToList<XElement>().ForEach(k =>
{
GC.Add(new GrandChildStep { Name = k.Attribute("name").Value });
});
value.Add(new ChildStep { Name = y.Attribute("name").Value, GrandChildStep = GC });
});
TreeViewTemplate.Add(new Step { Name = key, ChildStep = value });
});
}
catch (Exception)
{
}
}
}
Below is the custom class i have created
class Step
{
public string Name { get; set; }
public System.Collections.ObjectModel.ObservableCollection<ChildStep> ChildStep { get; set; }
}
class ChildStep
{
public string Name { get; set; }
public System.Collections.ObjectModel.ObservableCollection<GrandChildStep> GrandChildStep { get; set; }
}
class GrandChildStep
{
public string Name { get; set; }
}
You should expose everything you want to access on your (view) model and just bind to it, like the Background of the shape or the IsSelected property of the item (needs to be bound in ItemContainerStyle).
If you need to "access UI controls" in WPF you are usually doing something wrong.
name the child,for eg. in your case x:Name="Rect" to Rectangle
then
Declare this helper method in your Code
T GetVisualChild<t>(DependencyObject parent, string name) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<t>(v, name);
}
if (child != null)
{
break;
}
}
return child;
}
then
just declare Rectangle Rect=new Rectangle(); in constructor or loaded event.
and when you want to access the child. use that declared helper method. for eg.
Rect=GetVisualChild<treeview>(this, "Rect")
Note: here "treeview" is the name of parent You may give the name of parent accessing the child directly.

Execute RaiseCanExecuteChanged from 'subclass'

So I have the following setup:
PLANNING:
public class Planning : ViewModelBase
{
public Planning()
{
AddNewActivityCommand = new RelayCommand(AddActivity, CanAddActivity);
}
public ObservableCollection<PlanningItem> PlanningItems { get; set; }
public PlanningItem SelectedPlan { get; set; }
#region AddNewActivity
public RelayCommand AddNewActivityCommand { get; private set; }
private bool CanAddActivity()
{
if (!PlanningItems.Any())
{
return true;
}
if (string.IsNullOrEmpty(PlanningItems[PlanningItems.Count - 1].Activities) != true ||
PlanningItems[PlanningItems.Count - 1].DhpRepresentativeSelected != null)
{
return true;
}
return false;
}
private void AddActivity()
{
PlanningItems.Add(new PlanningItem());
AddNewActivityCommand.RaiseCanExecuteChanged();
}
#endregion
}
PLANNING ITEM:
public class PlanningItem : ViewModelBase
{
private string _activity;
public ObservableCollection<OutreachUser> DhpRepresentativeSource
{
get
{
var userSource = new ObservableCollection<OutreachUser>();
using (var context = new Outreach_Entities())
{
var query = from a in context.UserInfoes
join b in context.PersonalInfoes on a.UserIdentity equals b.PersonIdentity
join c in context.PersonalTitles on b.TitleLink equals c.TitleIdentity into cGroup
from c in cGroup.DefaultIfEmpty()
select new OutreachUser
{
PersonLink = a.UserIdentity,
Username = a.Username,
FirstName = b.FirstName,
MiddleInitial = b.MiddleInitial,
LastName = b.LastName
};
foreach (var result in query)
{
userSource.Add(result);
}
return userSource;
}
}
}
public OutreachUser DhpRepresentativeSelected { get; set; }
public DateTime PlanningDate { get; set; }
public TimeSpan PlanningStart { get; set; }
public TimeSpan PlanningEnd { get; set; }
public int PlanningTotalHours { get; set; }
public string Activities
{
get
{
return _activity;
}
set
{
_activity = value;
RaisePropertyChanged(nameof(Activities), "", _activity, true);
}
}
}
I have a ListBox bound to the PlanningItems Observable Collection.
I want to be able to add a new item to the list if the following criteria are met:
The Planning Items Collection is empty.
The last item in the Planning Items Collection has a DhpRepresentativeSelected that is not null.
The last item in the Planning Items Collection has some text in the Activities string.
The first item is easy enough because I call AddNewActivityCommand.RaiseCanExecuteChanged(); after I add a new item from an empty list.
Now I need to call the AddNewActivityCommand.RaiseCanExecuteChanged(); from within the PlanningItem ViewModel, but it does not have access rights to the command.
Clueless pointed me to the answer.
What I did was inside of my Planning ViewModel I created an internal Method that called the AddNewActivityCommand.RaiseCanExecuteChanged() method. I think called that method from within the PlanningItems ViewModel.

BindingList<> (master) with a composed BindingList<> (child) reference

I have a situation where a BindingList<> represents a collection of POCOs that have sub-collections of similar nature, Here is a sample code of two such POCOs and their respective lists:
The DirectoryTypePoco
public class DirectoryTypePoco : IBasePoco
{
public DirectoryTypePoco()
{
}
public DirectoryTypePoco(Int16 directoryTypeId, String directoryImplementation, String directoryDescription, DirectoryDefinitionPocoList directoryDefinition)
{
DirectoryTypeId = directoryTypeId;
DirectoryImplementation = directoryImplementation;
DirectoryDescription = directoryDescription;
DirectoryDefinition = directoryDefinition;
}
public Int16 DirectoryTypeId { get; set; }
public String DirectoryImplementation { get; set; }
public String DirectoryDescription { get; set; }
public DirectoryDefinitionPocoList DirectoryDefinition { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var lastMaxEntityId = repository.GetQuery<DirectoryType>().Select(select => #select.DirectoryTypeId).DefaultIfEmpty().Max();
var newEntity = new DirectoryType
{
DirectoryTypeId = (short)(lastMaxEntityId + 1),
DirectoryImplementation = this.DirectoryImplementation,
DirectoryDescription = this.DirectoryDescription
};
return newEntity;
}
}
And the BindingList<DirectoryTypePoco>:
public class DirectoryTypePocoList : BindingList<DirectoryTypePoco>
{
public DirectoryTypePocoList()
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>();
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
public DirectoryTypePocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
}
The second object: DirectoryDefinitionPoco
public class DirectoryDefinitionPoco : IBasePoco
{
public DirectoryDefinitionPoco()
{
}
public DirectoryDefinitionPoco(Int16 directoryTypeId, Byte parameterId, String parameterName, String parameterValidation, Boolean encryptionRequired, PocoChangeType changeType = PocoChangeType.None)
{
DirectoryTypeId = directoryTypeId;
ParameterId = parameterId;
ParameterName = parameterName;
ParameterDescription = parameterName;
ParameterRequired = false;
ParameterValidation = parameterValidation;
EncryptionRequired = encryptionRequired;
}
public Int16 DirectoryTypeId { get; set; }
public Byte ParameterId { get; set; }
public String ParameterName { get; set; }
public String ParameterDescription { get; set; }
public String ParameterValidation { get; set; }
public Boolean ParameterRequired { get; set; }
public Boolean EncryptionRequired { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var masterId = (short) parameters[0];
var lastMaxEntityId = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == masterId).Select(select => #select.ParameterId).DefaultIfEmpty().Max();
var newEntity = new DirectoryDefinition
{
DirectoryTypeId = (short)parameters[0],
ParameterId = (byte)(lastMaxEntityId + 1),
ParameterName = this.ParameterName,
ParameterDescription = this.ParameterDescription,
ParameterValidation = this.ParameterValidation,
ParameterRequired = this.ParameterRequired,
EncryptionRequired = this.EncryptionRequired
};
return newEntity;
}
}
And BindingList<DirectoryDefinitionPoco>:
public class DirectoryDefinitionPocoList : BindingList<DirectoryDefinitionPoco>
{
public DirectoryDefinitionPocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryDefinitionPoco(r.DirectoryTypeId, r.ParameterId, r.ParameterName, r.ParameterValidation, r.EncryptionRequired));
}
}
}
public List<DirectoryDefinition> GetSourceQuery()
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>().ToList();
}
return result;
}
public List<DirectoryDefinition> GetSourceQuery(short directoryTypeId)
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId).ToList();
}
return result;
}
}
On the form, I load the data into the grid through a BindingSource component. The child rows are added properly and the data is valid.
Here is the issue: I'm able to add new DirectoryTypePoco but when try to add a DirectoryDefinitionPoco, in the code, the the DirectoryDefinitionPocoobject that I get has a zero for it's parent object. In the above picture, the Test5.dll234 is a DirectoryTypePoco with DirectoryTypeId = 8 and all child under it are ok except the new one I create. What am I suppose to do to make sure I have Master-Child relation in this case?
Ok. It seems that there are two thing I should have noticed in my design.
The individual child Poco needs to know the parent Poco through a reference.
The DevExpress Grid has methods that allow for retrieving the attached data to a parent row while in the child view' particular row.
The first part is straightforwards: add a new property in the child poco of parent poco type.
This however, in my case, doesn't solve my issue as when I visually add a new row on the grid, the default constructor is invoked and it takes no parameters and hence the parent poco reference will remain NULL and the Ids (numeric) will be defaulted to 0
The second point helped fix my issue completely. I was able to conjure up an extension method for the XtraGrid's GridView as follows:
public static class DevExpressGridHelper
{
public static IBasePoco GetPocoFromSelectedRow(this BaseView view)
{
return (IBasePoco)view.GetRow(((GridView)view).FocusedRowHandle);
}
public static IBasePoco GetParentPocoFromSelectedRow(this GridView view)
{
if (view.ParentView !=null)
{
// return (IBasePoco)(view.ParentView).GetRow(((GridView)(view.ParentView)).FocusedRowHandle);
return (IBasePoco)((GridView)view.ParentView).GetFocusedRow();
}
return null;
}
}
And used it as follows:
private void GridMain_Level_1_RowUpdated(object sender, RowObjectEventArgs e)
{
var view = sender as GridView;
if (view == null)
{
return;
}
var pocoObject = e.Row as DirectoryDefinitionPoco;
if (pocoObject == null)
{
return;
}
var parentPocoObject = view.GetParentPocoFromSelectedRow();
if (parentPocoObject == null)
{
return;
}
if (view.IsNewItemRow(e.RowHandle))
{
Create(pocoObject, parentPocoObject);
}
else
{
Update(pocoObject);
}
}

MVVM and DBContext - how to put it together?

I'm trying to follow the MVVM pattern, however I spent some good time on this issue, googled a lot and checked stackoverflow as well... No working example found so far.
Basically, I've a simple application and want to retrieve and write data to SQL server. Here's my code:
//Model
public class Visitor
{
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
//ViewModel
public class VisitorViewModel : ViewModelBase
{
public ObservableCollection<Visitor> _visitorDataCollection = new ObservableCollection<Visitor>();
public ObservableCollection<Visitor> VisitorDataCollection
{
get { return _visitorDataCollection; }
set { _visitorDataCollection = value; }
}
private string _firstName = "";
private string _lastName = "";
public string FirstName
{
get { return _firstName; }
set
{
if (value != _firstName)
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return _lastName; }
set
{
if (value != _lastName)
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
}
public VisitorViewModel()
{
}
}
}
//VisitorContext class that represents a database context
public partial class VisitorContext : DbContext
{
public VisitorContext()
: base()
{
}
public DbSet<VISITOR> Visitors { get; set; }
}
}
Nothing really fancy. However, I cannot put it "together". How to complete that to retrieve all visitors and add a new one?
Could someone point me to the right direction?
Just a simple example how make it all to life.
Add some commands to VM:
public ICommand Add {get; private set;}
In constructor:
public VisitorViewModel()
{
using(var context = new VisitorContext())
{
//fill collection with initial data from DbContext
context.Visitors.ToList().ForEach(_visitorDataCollection.Add);
}
//setup add command, here I'm using MVVM Light like you
Add = new RelayCommand(()=> {
using(var context = new VisitorContext())
{
_visitorDataCollection.Add(context.Visitors.Add(new Visitor {
FirstName = this.FirstName,
LastName = this.LastName //read values from model properties
});
}
});
}
That's it, all you need to do is bind this ViewModel to appropriate View.

Resources