MVVM Binding between two user controls with separate view models - c#

i have a window which loads a Customer table and another user control of input fields, when i select i wish to populate the user control inputs. I currently have a datagrid that a selected item is set in the CustomerViewModel through binding. When this is selected it updates a textbox with the selected items property such as name, email, etc. I have a CustomerSettingsViewModel which contains multiple input fields. I am trying to bind the selected item to inputs within this model, however as the CustomerViewModel doesn't know about the CustomerSettingsViewModel i cant see the binds within the textbox inputs.
The views are loaded using DataTemplate using the datatype.
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type VM:CustomerVM}">
<View:Customers/>
</DataTemplate>
<DataTemplate DataType="{x:Type VM:CustomerSettingsVM}">
<View:CustomerSettings />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:SuppliersVM}">
<View:Suppliers/>
</DataTemplate>
<DataTemplate DataType="{x:Type VM:SuppliersSettingsVM}">
<View:SupplierSettings/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding SelectedMain}" Margin="0,135,0,10" Grid.ColumnSpan="2"/>
<ContentControl Content="{Binding SelectedSettings}" Margin="105,53,10,45"/>
These are set and get the correct views depending on the datatype that being presented.
MainVM.cs
Customer = new CustomerVM();
CustomerSettings = new CustomerSettingsVM();
SelectedMain = Customer;
SelectedSettings = CustomerSettings;
within the CustomerVM i have a get and selected with binds to anything within the customer view, however how can i get the customer settings view to see the selected customer has changed and populate the inputs?
CustomerVM.cs
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
_selectedCustomer = value;
RaisePropertyChangedEvent("SelectedCustomer");
}
}
i have upload a simple solution of my problem onto GitHub Might give a better understanding of what i am trying to achieve

I think you missed a few concepts about MVVM, maybe you should go back to basics.
Basically, your viewmodels have to be a "testable copy" of your view.
So if your target is to build a Customer View containing Customer Settings, what you need is:
a CustomerViewModel with a CustomerSettingsViewModel property
a CustomerView using CustomerViewModel as datacontext
a CustomerSettingsView declared into your CustomerView binded to the CustomerSettingsViewModel from the CustomerViewModel
Another way to put this: if you want a view to contain another view, you can have a viewModel to contain another viewModel.
This other question could show you how to use a vm as a property of another vm.
Please see my proposed solution using your GitHub example. Basically your problem is that you should not use directly the Customer Model in a View, but create a CustomerVm instead, and just delete the CustomerSettingVm.
You might understand better my implementation by reading how I'm used to deal with MVVM.
Hope it helps.

Related

Binding lost when using TabControl and MVVM

I still consider myself a beginner when it comes to MVVM and I am having an issue with a binding in a TabControl. My application allows the user to create nations and states, input some information and then save them to a database. Below is a descriptions of how my application is structured:
The base ViewModel/View is called ApplicationViewModel/ApplicationView. The ApplicationViewModel has an ObservableCollection called Tabs consisting of one AllNationsViewModel and one AllStatesViewModel. This Tabs property is bound to the TabControl's ItemsSource in the ApplicationView.
AllNationsViewModel/AllNationsView are used to display all the nations that have been entered by the user. It also allows the user to create new nations and select a particular nation for closer inspection. The AllStatesViewModel/AllStatesView do the same but for states.
Finally I have the NationViewModel/NationsView that deals with a particular nation; also here there are StateViewModel/StateView that do the same for a state.
For a nation you can at the moment only input a name but for a state you can input a name as well as the nation it is part of. The nation is selected using a ComboBox where all the nations created in the nations tab show up.
I use a static class called DataFacade as an interface to my data store. It is possible to add, remove and retrieve a list of nations/states using this interface; also it triggers events when something is added or removed.
The problem I am having is that when there is a state selected in the AllStatesViewModel (CurrentStateViewModel property) and I go to the nations tab and then back to the states tab the currently selected state has lost its nation. All the other states are still ok tough.
I will try to show the relevant code below (I have removed irrelevant code from some methods):
State class:
class State
{
public string Name { get; set; }
public Nation Nation { get; set; }
}
TabControl in the ApplicationView:
<TabControl ItemsSource="{Binding Tabs}" Margin="6">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding }" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
When the user creates a nation the AddCommand of the AllNationsViewModel is triggered:
private void Add(NationViewModel vm)
{
DataFacade.AddNation(vm.Nation);
}
The AllStatesViewModel gets notified when a nation gets added to the data store:
private void OnDataStoreNationsChanged(object sender, DataFacadeEventArgs e)
{
Nations.Add(new NationViewModel(e.Context as Nation));
}
The Nations property above is an ObservableCollection of NationViewModels. Now, this property is used by the ComboBox in the StateView to populate its items so a nation can be selected when creating/editing a state:
<ComboBox Grid.Row="1" Grid.Column="1" SelectedValue="{Binding Nation}" SelectedValuePath="Nation" ItemsSource="{Binding DataContext.Nations, RelativeSource={RelativeSource AncestorType={x:Type local:AllStatesView}}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I believe the problem has to do with the binding above. Because if I don't bind to the Nations property in AllStatesViewModel but instead bind to a temporary property in the ApplicationViewModel everything works. Can it be that the AllStatesView is thrown away by the TabControl when I go to another tab therefor the binding above sets the Nation property of my StateViewModel to null? When I debug I see that I get a null when exiting the states tab.
How would someone who is not a beginner like me solve this situation? I find my temporary solution rather ugly. I am not entirely sure how I should handle the data store access since all the MVVM examples I have found don't focus on this part.
EDIT: Addded some pictures as requested:
Atm I just have the simplest testing GUI set-up:
Here you see the AllNationsView, atm the NationView is only the "Name" TextBlock and the TextBox at the top.
Here is the AllStatesView, at the top is the currently selected state (displayed using StateView). Where you now see that USA is selected as nation for Montana, if I go to the Nations tab and then back to the States tab the nation for Montana is now blank. If I select Florida it still has USA as its nation.
WPF only keeps the UI for the active tab in memory. When you change tabs that UI is destroyed and the UI of the new tab is created and rebound.
There are a few ways around the problem you are having. You can use the Repository Pattern to store and access your data sources separate from the view models. Basically, an outside object holds your data sources, such as the lists of states and nations. That way they don't get destroyed when the active tab changes.
The other option is to store the data sources on your ApplicationViewModel and access them via a references to the ApplicationViewModel on each individual tab's view model. You shouldn't have to use a RelativeSource binding anywhere in that.

navigation/load different views on WPF/MVVM

I am quite new to WPF development, and currently I am trying to use the MVVM on my application development. I have read a lot about MVVM navigation and switching views, but I can't find a solution for my current situation. Let's explain what it is:
First of all, I have my main View element, a Dockpanel, with some fixed areas, and a main "dynamic" area where the content should change, depending on actions:
<DockPanel>
<Label Content="Top Fixed element"/>
<StackPanel Orientation="Vertical" Height="auto" Width="150" DockPanel.Dock="Left">
<Label Content="SomeOptions"/>
<!-- some more elements -->
</StackPanel>
<Label DockPanel.Dock="Bottom" Content="Foot"/>
<ContentControl Content="{Binding CurrentMainViewElementViewModel}"/>
</DockPanel>
I have defined some DataTemplates that I would like to load in this ContentControl, here there is one of the Data Templates as example:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:FileLoaderVM}">
<View:FileLoaderView/>
</DataTemplate>
</Window.Resources>
This FileLoader (View and View Model are implemented, using the RelayCommand and the INotifyPropertyChanged) opens a dialog box after clicking a button, where after selecting a file it is opened and parsed, and show all the found elements inside a ListView with multiple selection(in this case, persons with their data).
What I want to do now is to load another user control in this ContentControl, when I click a button. This button is defined in my view model like this:
public ICommand LoadPersons
{
get { return new RelayCommand(param => this.loadSelectedPersons(), param => (SelectedPersons!=null && SelectedPersons.Any()));}
}
My question comes at this point, how can I modify the content of the ContentControl, loading another User Control instead of the current one directly from my view model (in this "this.loadSelectedPersons()")?
If this is not possible, how should I approach to solve this problem?
Next to this action, I want to show all the previously selected elements and manipulate in different possible ways (inserting in a DB, saving in another file and so on), and I have already for that the appropriate User Control, that I would like to show in my main view element in the ContentControl section, keeping the other elements as they are originally.
lets see if i get you right.
you have a mainviewmodel with a property (CurrentMainViewElementViewModel) bound to the ContentControl. your MainViewmodel set the FileLoaderVM to this Property. now you wanna show a "new/other" Viewmodel when a File is seleted in your FileLoaderVM?
why dont you simply expose a event from your FileLoaderVM and subscribe to this event in your MainViewModel? if you do so your MainViewModel can then set the "new/other" Viewmodel to the ContentControl
To change content of ContentControl you do not load another user control, but change value of CurrentMainViewElementViewModel (to which ContentControl.Content is bound) to a new ViewModel, which will load another UserControl (defined in DataTemplate same way as FileLoaderVM is).
This looks like a job for main ViewModel (where CurrentMainViewElementViewModel is located).
Easiest solution is to provide a method in that ViewModel
public Switch()
{
CurrentMainViewElementViewModel = SomeViewModel;
}
and call this method from FileLoaderVM.

Bind multiple views to multiple viewmodels

I have a WPF window displaying different self-defined Views. So far I was able to use everything I learned about MVVM :)
Now I got to a new "problem": I have 10 entities of the same view in a bigger view. These ten view-entities contain a set of controls (textbox, combobox etc.) but are all consistent.
So how do I bind these Views to a ViewModel?
I thought about having 10 instances of the ViewModel in the "higher-level" ViewModel and give the views fix-defined the instances of the VM as datacontext.
My question is now --> Is there a easier (or more convienient) way to bind many (identical) views to their viewmodels?
Code-Example:
View Model:
private PanelViewModel _panelViewModel1 = new PanelViewModel();
public PanelViewModel PanelVM1
{
get { return _panelViewModel1; }
}
View-Example:
<myControls:vwPanel HorizontalAlignment="Left" x:Name="vwPanel1"
VerticalAlignment="Top" DataContext="{Binding Path=PanelVM1}"/>
What bothers me is that I would need this logic ten times for ten views?
UPDATE:
To answer some questions: I want to show one view 10 times (in my example) I defined my own view by inheriting from UserControl. So my vwPanel inherits from UserControl. The 10 vwPanels are just placed inside a StackPanel inside a Grid.
It's not about displaying data, as you pointed out, there would be a listview or a datagrid a better place to start. It's a special case where I need this much input-controls :/
UPDATE2: What I hoped for was more like defining a List of ViewModels and Bind my 10 Views to one of this List. But this will not work will it? At least I wouldn't know how to refernce one "special" entitiy in the list out of XAML...
Typically I use implicit DataTemplates for mapping Views to ViewModels. They can go in <Application.Resources>, <Window.Resources> or even in under specific elements only such as <TabControl.Resources>
<DataTemplate DataType="{x:Type local:PanelViewModel}">
<myControls:vwPanel />
</DataTemplate>
This means that anytime WPF encounters an object in the VisualTree of type PanelViewModel, it will draw it using vwPanel
Objects typically get placed in the VisualTree through an ItemsSource property
<ItemsControl ItemsSource="{Binding CollectionOfAllPanels}" />
or by using a ContentControl
<ContentControl Content="{Binding PanelVM1}" />
If I understand your question correctly, you have a collection of something that you what to represent visually. That is, you have several viewmodels that you want to define a single view for, but show X number of times. Your example shows you using a panel as your view for the "PanelViewModel"...what is the parent item's control for the vwPanel? Assuming you're using something like a ListBox, you can define a custom DataTemplate that contains your vwPanel and assign that DataTemplate to your ListBox.ItemTemplate.
For example:
<Window.Resources>
<DataTemplate x:Key="myVMTemplate" TargetType="{x:Type myViewModels:PanelViewModel}">
<myControls:vwPanel HorizontalAlignment="Left" VerticalAlignment="Top"/>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Path=MyCollectionOfPanelVMs}"
ItemTemplate="{StaticResource myVMTemplate}" />
I haven't verified that this works.

How do I set view/view model data template at runtime?

This MVVM stuff is making my head hurt. I have an application which has a list of editors in a left pane. On the right is a tab control where the editors will be displayed. I have a main application view model that contains a collection of view models. I call this collection Workspaces. This is borrowed from the MvvmDemoApp that Microsoft provides here.
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
...
}
}
These workspaces are bound to a tab control in the main application window like so:
<DataTemplate x:Key "WorkspacesTemplate">
<TabControl
IsSynchonizedWithCurrentItem="True"
ItemSource="{Binding Workspaces}"
SelectedItem="{Binding ActiveWorkspace}"/>
</DataTemplate>
...
<ContentControl
Content="{Binding}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>
The view models are tied to a view using DataTemplates like so:
<DataTemplate DataType="{x:Type vm:MessageLogViewModel}">
<vw:MessageLogView/>
</DataTemplate>
This works fine. However, now I need to make the application configurable where the list of editors are read from a config file. I imagine this config file will contain the view and view model components for each editor. But, how do I tie the two together so that when someone binds to a view model (or a collection of view models), the correct view gets displayed (similar to what the DataTemplate does but in code, not XAML)?
I'm trying to stay away for Inversion of Control (IoC) techniques. I'm not sure our team is ready for that must sophistication.
IoC is the perfect solution for this however without this option you could creating the XAML data template in the view model using an XmlWriter and expose it as a property to bind to.
Edit: Bindings
You have your list of view models. Create and expose this XamlTemplate property in each view model (in a base view model class). The property should create Xaml along the lines of:
<DataTemplate xmlns:vw="...">
<vw:MessageLogView/>
</DataTemplate>
Then use a ContentControl to bind to:
<ContentControl Content="{Binding ViewModel}"
ContentTemplate="{Binding ViewModel.XamlTemplate}" />

Loading XAML at runtime using the MVVM pattern in WPF

This is a question that extends from the originally posted here:
Link to loading-xaml through runtime
I'm working on a WPF MVVM application that loads XAML content dynamically from an external source, very similar as the answer in the post above.
Here is what I got so far:
My View declares an instance of the ViewModel as a resource and creates an instance of that ViewModel
In my ViewModel constructor I'm loading a XamlString property coming from an external source (file or db..)
In my view I have a button that user clicks after ViewModel finishes loading and in the click-event code-behind I'm deserializing the dynamically loaded XAML and add it to my grid.
My question is, how can I eliminate code-behind and automate the logic so the View can render the new xaml section dynamically right after the ViewModel is done getting the XAML content and initializing the string property?
Should I use some kind of Messaging Bus so the ViewModel notifies once the property has been set so the View can add the new content?
What troubles me is the fact that ViewModels do have a reference to Views and should not be in charge of generating UI elements.
Thanks in advance!
Edit:
Just to clarify: in my particular case I am not trying to bind a Business Object or Collection (Model) to a UI element (e.g. Grid) which obviously could be accomplished through templates and binding. My ViewModel is retrieving a whole XAML Form from an external source and setting it as a string property available to the View. My question is: Who should be in charge of deserializing this XAML string property into a UI element and add it programmatically to the my grid once my Xaml string property in the VM is set?
This sounds to me more of like a View responsibility, not ViewModel. But the pattern as i understand it enforces to replace any code-behind logic with V-VM bindings.
I have a working solution now and I'd like to share it. Unfortunately I did not get rid of code-behind completely but it works as I expect it to. Here is how it works(simplified):
I have my simplified ViewModel:
public class MyViewModel : ViewModelBase
{
//This property implements INPC and triggers notification on Set
public string XamlViewData {get;set;}
public ViewModel()
{
GetXamlFormData();
}
//Gets the XAML Form from an external source (e.g. Database, File System)
public void GetXamlFormData()
{
//Set the Xaml String property
XamlViewData = //Logic to get XAML string from external source
}
}
Now my View:
<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
<Grid x:Name="content" Grid.Row="1" Margin="2"/>
<!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
<TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
</StackPanel>
</Grid>
Basically I created a hidden TextBlock bound to my XAML String property in the ViewModel and I hooked its Loaded event to an event handler in the code behind of the View:
private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
{
//First get the ViewModel from DataContext
MyViewModel vm = content.DataContext as MyViewModel;
FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
//Add the XAML portion to the Grid content to render the XAML form dynamically!
content.Children.Add(rootObject);
}
This may not be the most elegant but gets the job done. Like some people say, in MVVM there are some cases like this where little code-behind code is needed. It doesn't hurt and also part of this solution still uses the V-VM Binding principles when using the VM to retrieve and populate the XamlString property and exposing it to the View. If we would like to Unit Test the XAML parsing and loading functionality we could delegate it to a separate class.
I hope someone finds this useful!
I'm having trouble understanding what you're saying, so my answer will be based on my interpretation. You should consider posting a sample (simplified) of what you're trying to do.
1) I think you're misunderstanding what MVVM does. MVVM is mostly a binding-based pattern. Your view model should be exposing properties containing business objects and your view should just be binding to those properties. If I am misunderstanding you, and that's what you are doing, then your problem is that your view needs to be aware of when the properties get updated (after you deserialize your xaml, etc). There are two ways to do this: INotifyPropertyChanged interface on your viewmodel, or make your view model inherit from DependencyObject, and make the properties dependency properties. I won't go into details here, because this is a large subject that you should research on Google before making a decision.
2) Generally speaking, you shouldn't use click events inside your view if you're using MVVM. Instead, create properties on the view model of type ICommand (and create ICommand implementations to match, or use an implementation of DelegateCommand (google it) which will allow you to use delegates to implement the interface. The idea is, your view binds to the property and executes the handler directly inside the viewmodel.
3) If you want to push information from the viewmodel to the view, then you should create an event on the viewmodel and subscribe to it in the view, but this is a last resort, only to be used in cases like displaying a new window, etc. Generally, you should be using binding.
4) To be more specific about what you're doing, you should be binding your Grid's ItemsSource property to some property on the view model. Note, the property on the view model should be of type ObservableCollection<T> if you want to be able to add items and get instant updates.
Hope this helps.

Categories