MVVM and nested view models - wpf

I have a simple example where I'm creating a View consisting of a list box, and the list box displays a bunch of items. I'm wondering if I'm going about the creation of the View Model and Model classes correctly here. Use whatever value of correctly works in this context, I understand it's a bit subjective, but my current solution doesn't feel right. Here's a simplified version.
The ViewModels and Models:
namespace Example
{
public class ParentViewModel
{
public ParentViewModel()
{
// ... Create/Consume ChildViewModel * n
}
public List<ChildViewModel> ChildViewModels { get; set; }
}
public class ChildViewModel
{
public ChildViewModel()
{
// ... Create/Consume ChildModel
}
public ChildModel Model { get; set; }
}
public class ParentModel
{
public List<ChildModel> ChildModels { get; set; }
public ParentModel()
{
// ... Create/Consume ChildModel * n;
}
}
public class ChildModel
{
public ChildModel()
{
// ... Contains actual data.
}
public string Data { get; set; }
}
}
The View:
<Window x:Class="Example.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Example="clr-namespace:Example" Title="View" Height="300" Width="300"
DataContext="{StaticResource TheViewModel}">
<Window.Resources>
<Example:ParentViewModel x:Key="TheViewModel" />
</Window.Resources>
<Grid>
<ListBox Height="261" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="278" ItemsSource="{Binding ChildViewModels}"/>
</Grid>
In the proper code, the listbox will use a data template to display the child view models. But as you can see I'm not sure how the to instantiate the child related objects. It feels like the ParentViewModel will have a reference to the ParentModel and create ChildViewModel objects based on the ParentModel's ChildModel objects. Now I've said that it doesn't sound so daft, but I'd be interested in your thoughts.

You are on the right track.
The parent model would naturally contain a list of child models, e.g. a customer having multiple orders.
When ParentViewModel is created and loaded by a third-party, it is passed a ParentModel. Then the ParentViewModel will:
Assign the ParentModel to a local variable
Create a ChildViewModel for each ChildModel by passing the
ChildModel to the ChildViewModel constructor
Add each of those ChildViewModels to a list
By the way, you want
public List<ChildViewModel> ChildViewModels { get; set; }
to be
public ObservableCollection<ChildViewModel> ChildViewModels { get; set; }

Related

How to bind an ObservableCollection to a combobox in WPF?

I'm stuck with this and even after reading a lot of topics I can find the answer.
Here my attempt to bind an observable Collection to a combobox in WPF using MVVM Pattern:
Scenario.cs
{
public class Scenario
{
public string name { get; set; }
public string codeClient { get; set; }
public string codeAppli { get; set; }
public string infoComplementaire { get; set; }
}
}
scenarioVM.cs
{
public ObservableCollection<Scenario> Scenarios { get; set; }
}
MainWindows.xaml
<ComboBox x:Name="cbScenario" ItemsSource="{Binding Scenarios}" DisplayMemberPath="{Binding Path=Name}" HorizontalAlignment="Left" Margin="407,8,0,0" VerticalAlignment="Top" Width="226" BorderBrush="#FF1585B5" Height="26"/>
Thanx for the help :)
If you set your DataContext to this, ofc you can't find Scenarios if its part of your ScenarioVM and not your Window. DataContext is the root of any Binding.
For a start you could do this.
public ScenarioVM VM {get; private set;}
public MainWindow()
{
VM = new ScenarioVM();
InitializeComponent();
DataContext = VM;
this.Loaded += MetroWindow_Loaded;
VM.Scenarios.Add(new Scenario());
}
Now your window owns an instance of the ScenarioVM. Not necessarily a good design, but a start.
Also sooner than later you run into Trouble that your Scenario is not deriving from INotifyPropertyChanged so you might want to address that as well.

Binding to property of collection elements

I use MVVM pattern in my WPF application.
I have ObservableCollection Records in my ViewModel.
public enum RecordState
{
NotChanged,
Changed,
Added,
Deleted,
AlreadyExist
}
public class Record
{
public string FirstId { get; set; }
public RecordState State { get; set; }
public string CurrentId
{
get { return GetIdFromInstance(Instance); }
}
public MyStronglyTypedClass Instance { get; set; }
}
public class MyViewModel
{
public ObservableCollection<Record> Records;
// other code
}
In View i have DataGrid.
<DataGrid ItemsSource="{Binding }" //>
What i have to write(if that possible) in ItemsSource="{Binding /* here */}", so that Datagrid Items changed to
Records[0].Instance
Records[1].Instance
Records[2].Instance
...
Records[Records.Count-1].Instance
{Binding} means that your ItemsSource is the DataContext of that DataGrid(probably inherited from ancestor elements).
What you should do is set the DataContext of your top-level element(Window, UserControl etc..) to your ViewModel class.
And then as Gary suggested:
<DataGrid ItemsSource="{Binding Records}">
The link you gave about dynamic elements does the same in that matter, it adds more complicated element bindings and DataTemplates.

WPF Combobox initial dictionary binding value not showing

I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}

Can a View have two View Models as its Data Context?

I have two datagrids in a single view but the collections which are ItemsSource of these datagrids are in different View Models. So is it possible to bind these two datagrids with the collections in two different View Models?
Go for a view model combining both:
public class ViewModelA {
public ObservableCollection<CustomClass> Items { get; set; }
/* properties, etc. */
}
public class ViewModelB {
/* properties, etc. */
}
public class CombiningViewModel {
public ViewModelA A { get; set; }
public ViewModelB B { get; set; }
}
Binding can be done like
<DataGrid ItemsSource="{Binding A.Items}">
<!-- Sample, not complete -->
</DataGrid>
No, not directly. You do have options though:
You could set the DataCOntext of the view to itself, then expose each viewmodel through a separate property and bind to those properties:
public class MyView : Window
{
public MyView()
{
this.DataContext = this;
}
public ViewModel1 FirstViewModel { get; set; }
public ViewModel2 SecondViewModel { get; set; }
}
Or you could make a wrapper viewmodel which either extends (inherits from) one of the viewmodels, or wraps them both and surfaces the appropriate properties:
public class MyCompositeViewModel
{
public ViewModel1 FirstViewModel { get; set; }
public ViewModel2 SecondViewModel { get; set; }
}
You can set the DataContext for each DataGrid rather than for the container view.
<Grid>
<DataGrid ... DataContext="..." />
<DataGrid ... DataContext="..." />
</Grid>
Or don't use a DataContext and Bind to the models directly
<DataGrid ItemsSource="{Binding Source={StaticResource ...}}" />

Silverlight DataContext Databinding behavior

I'll start off with a stripped-down/sanitized version of my code:
Model:
class DataObj : INotifyPropertyChanged {
// these actually call OnPropertyChanged, and have associated private variables
public string Name { get; set; }
public int Age { get; set; }
}
class DataContextObj : INotifyPropertyChanged {
public List<DataObj> DataItems { get; set; }
}
View:
<StackPanel x:Name="MyPanel">
<TextBlock Text="{Binding Path=DataItems[0].Name}" />
<TextBlock Text="{Binding Path=DataItems[0].Age}" />
</StackPanel>
View code-behind:
//in the constructor
MyPanel.DataContext = new DataContextObj();
Now, my question is, if the DataItems list is initialized but empty, what is the expected behavior when something tries to bind to, say, the first element in the list? My understanding is that it just ignores the binding; is that true?
Yes it will ignore the binding. If subsequently an item is added to the empty list the text blocks will not update since the binding expression associated with them will not know that the change happened.
The appropriate solution is to use:
public class DataContextObj
{
public ObservableCollection<DataObj> DataItems {get; private set; }
}
Additions to the collection will notify "Item[]" has changed which will allow the binding expression to re-evaluate.

Resources