Accessing multiple view models - wpf

I have 2 simple user controls and 2 view models that bind to them. I now want to add these to a master user control but want to know the correct way of accessing ViewModel properties from View 1 in my View 2 in the text block named FromView1?
My View 1:
<UserControl x:Class="v1"
.....
<Grid>
<TextBlock Text="{Binding Text1}" />
</Grid>
</UserControl>
My View 2:
<UserControl x:Class="v2"
..
<Grid>
<StackPanel>
<TextBlock Text="{Binding Text2}" />
<TextBlock x:name="FromView1" Text="{Binding Text1}" Background="Red" Width="100" />
</StackPanel>
</Grid>
</UserControl>
My main view containing View 1 and View 2
<UserControl x:Class="vMain"
....
<Grid Margin="20">
<StackPanel>
<local:v1 x:Name="v1"/>
<local:v2 x:Name="v2"/>
</StackPanel>
</Grid>
</UserControl>

Instead of having two separate view models, you should merge them into one single MainViewModel. v1 and v2 would should then share the DataContext with the vMain view which means they can bind to whatever property they want do.
So simply set the DataContext of vMain to an instance of MainViewModel and make sure that you don't set the DataContext of v1 or v2 explicitly somewhere.
UserControls should generally speaking not have their own view models. They should inherit the DataContext from their parent, which is usually a window.
If your view models tend to get big, you could use partial classes to split the definitions across several source code files.

your mainView's Viewmodel need to have ViewModel1 and ViewModel2
public class MainViewModel
{
....
public ViewModel1 vm1 {get;set;}
pulbic ViewModel2 vm2 {get;set;}
....
}
Xaml is ..
<UserControl x:Class="vMain"
....
<Grid Margin="20">
<StackPanel>
<local:v1 x:Name="v1" DataContext="{Binding vm1}"/>
<local:v2 x:Name="v2" DataContext="{Binding vm2}"/>
</StackPanel>
</Grid>
</UserControl>

Related

Assign different ViewModels to one UserControl

I have MainWindow.xaml page with it's MainViewModel
and would like to add 2 SidePanels using one UserControl, but it should have different ViewModels.
MainViewModel alredy has 2 properties with created SidePanelViewModels:
public MainViewModel()
{
LeftSidePanel = new SidePanelViewModel(PanelSides.Left);
RightSidePanel = new SidePanelViewModel(PanelSides.Right);
}
How to set objects in this properties as DataContext for each UserControl in xaml?
Something like this doesnot work:
<Window DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
...
<Grid Visibility="{Binding RightSidePanel.PanelVisibility}" Grid.Column="4" Grid.Row="2" >
<v:SidePanelViev DataContext="{Binding RightSidePanel}" />
</Grid>
</Window>
I broke all the brains thinking how to do it, Please help
ps.
Or please suggest any other approach to reach the same target..
I have solved my problem by adding both instances of SidePanelViewModel as Content of ContentControl
<Window DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
...
<Grid Visibility="{Binding RightSidePanel.PanelVisibility}" Grid.Column="4" Grid.Row="2" >
<ContentControl Content="{Binding RightSidePanel}"></ContentControl>
</Grid>
</Window>
and adding new Window.Resource what binds all classes of type SidePanelViewModel to be visualised using SidePanelViev
<Window.Resources>
<DataTemplate DataType="{x:Type vm:SidePanelViewModel}">
<v:SidePanelViev></v:SidePanelViev>
</DataTemplate>
</Window.Resources>
This works, but if somebody has better ideas, please do not hesitate to post them, I am not sure that my decision is good one

ActivateItem not working in Caliburn.Micro

I am using Caliburn Micro for MVVM. In my MainView (shell), I have two controls. One hosts a RibbonView and another ContentControl which loads contents depending on the RibbonView menu selection. Here is the MainView (shell)
MainView (shell)
<Window x:Class="HotelReservation.Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" >
<ContentControl x:Name="ActiveItem"/>
</Grid>
</DockPanel>
</Window>
RibbonView
<Ribbon Margin="0,-20,0,0">
<RibbonTab Header="Room Band">
<RibbonGroup>
<RibbonButton Label="List" x:Name="RoomBandMain"
LargeImageSource="/HotelReservation.Global;component/Images/room-band-list-icon.png">
</RibbonButton>
</RibbonGroup>
</RibbonTab>
</Ribbon>
RibbonViewModel
public class RibbonViewModel : Conductor<object> {
public void RoomBandMain() { //Load in ActiveItem of MainView
ActivateItem(container.GetExportedValue<RoomBandMainViewModel>());
}
}
As can be seen, I am trying to load RoomBandMainViewModel in the <ContentControl x:Name="ActiveItem"/> The issue is that it is not loaded and I get a blank screen even though ActivateItem(container.GetExportedValue<RoomBandMainViewModel>()) code runs. I think that the <ContentControl x:Name="ActiveItem"/> exists not in RibbonView but its parent MainView, and hence the ActivateItem doesn't work.
How to resolve this issue.
Edit:
I had to set the DataContext of the <ContentControl x:Name="ActiveItem"/> to RibbonViewModel, so that ActiveItem is now property of RibbonViewModel and not MainViewModel. MainViewModel looks like below
So the MainView (shell) is now as follows
<Window x:Class="Conductor_Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" Background="Green"
DataContext="{Binding RibbonView}">
<ContentControl x:Name="ActiveItem" />
</Grid>
</DockPanel>
</Window>
Now the <ContentControl x:Name="ActiveItem" /> actually belongs to the RibbonViewModel.
What you have here is some kind of lifecycle of your windows. This has to be handled by the parent window of your ActiveItem.
The way to get this done the caliburn.micro way is to have a Conductor above the ActiveItem. In your case this is the MainWindow.
Your RibbonViewModel can be a Conductor, too. But only for it's own children. There can be more than one conductor.
From the caliburn documentation
Once you introduce the notion of a Screen Activation Lifecycle into
your application, you need some way to enforce it. This is the role of
the ScreenConductor. When you show a screen, the conductor makes sure
it is properly activated.
Which is quite a direct way of saying: If you have activation / life cycle, then use a conductor.

display content class xaml vb.net

Well i have a mainWindow written in xaml (vb.net) with 10 buttons and for each button i have a class (with buttons...) in my project. Example: when i click on button valid i display the content of the class valid in my grid. Each class is instantiated in the class mainWdow.
I also have a grid on this mainWindow.
When i click on a button in my mainWindow, i would like to display the content of the correspondig class in my grid and of course i want to be able to use this class (click on buttons...).
What's the best way to do this on vb.net / xaml please?
On this webpage http://msdn.microsoft.com/en-us/library/cc903947(v=vs.95).aspx there are some indications but it is not what i want because i don't want just only display the content on my class but i want to be able to work with the class displayed on my grid...
I give you thanks for your advices.
Ok, so first you should read up about DataTemplates from the Data Templating Overview page on MSDN. Now here is the basic principal... instead of putting your reusable XAML into Windows, declare them in DataTemplates in your Window.Resources section:
<DataTemplate x:Key="StopDataTemplate">
<!--Define your content here-->
</DataTemplate>
<DataTemplate x:Key="ValidDataTemplate">
<!--Define your content here-->
</DataTemplate>
Now you can display the content of these DataTemplates in a ContentControl:
<ContentControl Name="Content" ContentTemplate="{StaticResource ValidDataTemplate}" />
Then when you want to switch to the other view, you just need to set the ContentControl.ContentTemplate property to the other DataTemplate (from Window code behind):
DataTemplate dataTemplate = (DataTemplate)FindResource("StopDataTemplate");
Content.ContentTemplate = dataTemplate;
Well, here is what i have done to do some tests:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="CBTemplate">
<Grid Height="200" HorizontalAlignment="Left" Name="centralGrid" VerticalAlignment="Top" Width="200" Background="Red">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding Title}"
Margin="10" HorizontalAlignment="Left" FontSize="20"/>
</Grid>
</DataTemplate>
then in my mainWindow.xaml i have this:
<Grid x:Name="layout" Background="LightBlue" Margin="250,150,260,380">
<ContentControl Name="Content" ContentTemplate="{StaticResource CBTemplate}" />
</Grid>
And in the mainWindow.vb i have:
Class MainWindow
Private t As Thing
Sub New()
InitializeComponent()
t = New Thing("test")
layout.DataContext = t
End Sub
End Class
So my program compiles properly but there is a little problem: text="{Binding Title}" doesn't work and when i replace it with "toto", there is no problem toto is displayed...
Did i miss something in my code?

WPF: Set UserControl DataContext

I have the following MainWindow that lays out a left side navigation panel and a right side display area (both of these are UserControls).
Can someone explain how to assign the DataContext of the navigation panel (LinksView.xaml) to that of LinksViewModel.cs. I would like to bind a Command (BtnCompanyClickCommand) to the button and define BtnCompanyClickCommand in LinksViewModel.cs.
I have tried various methods that I found on StackOVerflow to set the DataContext but none of these solutions seem to work (binding RelativeSource, naming view and binding to name, etc.).
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding ElementName=Links,Path=BtnCompanyClickCommand}" />
</StackPanel>
FormsDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SidekickAdmin.ViewModel"
xmlns:vw="clr-namespace:SidekickAdmin.View">
<DataTemplate DataType="{x:Type vm:CompanySummaryViewModel}">
<vw:CompanySummaryView>
<ContentControl Content="{Binding }" />
</vw:CompanySummaryView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>
</ResourceDictionary>
EDIT
So I finally came across this explanation of how to set the DataContext of a UserControl which has to be done on the first child item of the UserControl.
Here is the modified LinksView.xaml that works.
<StackPanel Orientation="Vertical">
<StackPanel.DataContext>
<vm:LinksViewModel /> <!-- Bind the items in StackPanel to LinksViewModel -->
</StackPanel.DataContext>
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
However, I am still not clear on why I have to set the DataContext of the child element and not the UserControl and why the DataTemplate for LinksView (set in FormsDictionary.xaml) doesn't tie into the DataContext of LinksViewModel. Any explanation would be appreciated.
First you have to refer to your DataContext (LinksViewModel.cs) in your XAML code.
You can do that either by directly instantiating it or use a ResourceDictionary. In the latter case you instantiate your DataConext either inside some .cs file or inside the ResourceDictionary .xaml file and store it in a named ResourceDictionary where you can find the reference later.
Second you simply have to associate the DataContext property of a View element like your LinksView.xaml with the corresponding DataContext.
This is pretty high-level and without any code but that's the basic idea behind it.
there should be an instance of LinksViewModel in MainWindowViewModel:
MainWindowViewModel.cs:
class MainWindowViewModel
{
public MainWindowViewModel()
{
LinksVM = new LinksViewModel();
}
public LinksViewModel LinksVM { get; private set; }
}
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding LinksVM}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
since LinksView is explicitly created in MainWindow, there is no need in DataTemplate - it can be removed
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>

ListBox.ItemsSource binding in code and in xaml

I wrote simple code like
public ObservableCollection<string> Names …
public Window1()
{
PutInDataIntoNames();
InitializeComponent();
this.listBox1.ItemsSource = Names;
}
and in xaml
<Grid>
<ListBox Margin="10,11,10,16"
Name="listBox1"
Background="Black"
Foreground="Orange"
/>
</Grid>
Then I wanted to set ItemsSource property in xaml. In order to do that I wrote the following:
ItemsSource="{Binding Path=Names}"
Unfortunately, it doesn’t work. Could you explain why and how to do that right?
If you only specify the binding path the binding engine will try to navigate the path starting from the current DataContext so ItemsSource="{Binding Path=Names}" does not work like this, there are a lot of different things to keep in mind especially when doing more complex things.
The single most important article that everyone who is new to DataBinding should read is the Data Binding Overview on MSDN
To get back to your binding, if you want to do it completely in XAML you can do that as well, you just need to make the Window your source somehow, either by referencing it directly or relatively or by setting it up as the DataContext.
1 - Direct Reference:
<Window Name="Window"
...>
<Grid>
<ListBox ...
ItemsSource="{Binding ElementName=Window, Path=Names}"
.../>
</Grid>
</Window>
2 - Relative Reference
<Grid>
<ListBox ...
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Names}"
.../>
</Grid>
3 - Setting up the DataContext
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid>
<ListBox ...
ItemsSource="{Binding Path=Names}"
.../>
</Grid>
</Window>
Do this in code behind
public Window1()
{
PutInDataIntoNames();
InitializeComponent();
DataContext = this;
}
and in XAML
<Grid>
<ListBox ItemsSource="{Binding Names}"
Margin="10,11,10,16"
Name="listBox1"
Background="Black"
Foreground="Orange"
/>
</Grid>
Ideally you should follow MVVM design to isolate data from code behind.
It seems that your Names might be a field. You can ONLY bind to public properties

Resources