mvvm navigation treeview - wpf

i'm trying to implement a navigation menu using a treeview.
on the left panel there is a treeview and on the right panel the matched view.
since it's MVVM i'm having a difficult to switch between the correct views.
clicking on Menu1 - should display View1.xaml view
clicking on Menu2 - should display View2.xaml view
my code looks like that:
MainView.xaml
<Window x:Class="Menu.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:Menu.View"
Title="MainView" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type Views:Page1}">
<Views:Page1 />
</DataTemplate>
<DataTemplate DataType="{x:Type Views:Page2}">
<Views:Page2 />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Grid DockPanel.Dock="Left">
<TreeView>
<TreeViewItem Header="Menu 1" />
<TreeViewItem Header="Menu 2" />
<TreeViewItem Header="Menu 3" />
</TreeView>
</Grid>
<Grid DockPanel.Dock="Right">
<Views:Page1 />
<Views:Page2 />
</Grid>
</DockPanel>
</Window>
Page1.xaml (the view that should be visible when clicking "Menu 1")
<Grid>
<Label FontSize="24" FontWeight="Bold">1</Label>
</Grid>
Page2.xaml (the view that should be visible when clicking "Menu 2")
<Grid>
<Label FontSize="24" FontWeight="Bold">2</Label>
</Grid>
for every page i have its own ViewModel and i have the main one called MainViewModel.
how should i implement such thing in a MVVM mode ?

I think here is the mistake. You should have put your ViewModels in the DataType. so when you fill the DataContext the DataTemplate do his job. something like :
<DataTemplate DataType="{x:Type ViewModels:Page1ViewModel}">
<Views:Page1 />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:Page2ViewModel}">
<Views:Page2 />
</DataTemplate>
instead of :
<Views:Page1 />
<Views:Page2 />
add a ContentControl and do a binding to it's content like this :
<ContentControl Content="{Binding MyViewModel}"></ContentControl>
myView is property in your MainViewViewModel (which has to implement INotifyPropertyChanged) can be like this :
object _MyView;
public object MyViewModel
{
get
{
return _MyView;
}
set
{
_MyView = value;
OnPropertyChanged("MyViewModel");
}
}
now, in each TreeViewItem add a Command that do update it's Content for ex :
in your MainViewViewModel add CallView1Command property and implement it (see how dealing with commands)
so in Command's excute method you can update MyViewModel according to the View you want to show.
I recommend using Unity to instanciate ViewModels.
Not very well explained, but, Hope it helps anyway

Related

How to resolve content presenter not displaying View?

I've added a content presenter to my TabControl's data template, in order to display the correct view.
But when I load the application, the tabs display but they have no user control content.
I Googled the error and came across this solution, that suggested an error with the data context but the setup seems ok in my AppVM and AppView below.
The names of both VM's and Views are also correct that I'm referencing in the AppView.
Does anyone have an idea where the setup has gone wrong here?
This is the ApplicationView that holds both views:
<Window x:Class="MongoDBApp.Views.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MongoDBApp.Views"
xmlns:vm="clr-namespace:MongoDBApp.ViewModels"
Title="ApplicationView"
Width="800"
Height="500">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CustomerDetailsViewModel}">
<views:CustomerDetailsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CustomerOrdersViewModel}">
<views:CustomerOrdersView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<vm:ApplicationViewModel />
</Window.DataContext>
<TabControl ItemsSource="{Binding PageViewModels}" TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding CurrentPageViewModel}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
ApplicationViewModel constructor and related fields:
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
private static ICustomerDataService customerDataService = new CustomerDataService(CustomerRepository.Instance);
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationViewModel"/> class.
/// </summary>
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new CustomerDetailsViewModel(customerDataService));
PageViewModels.Add(new CustomerOrdersViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
The ContentTemplate property wraps the Content object.
For example, with your code you are setting the .Content property to a CustomerDetailsViewModel object, and trying to bind to the CurrentPageViewModel of that object, which doesn't exist.
What is getting rendered is :
<TabControl>
<TabItem>
<ContentPresenter Content=CustomerDetailsViewModel>
<ContentPresenter Content="{Binding CurrentPageViewModel}" />
</ContentPresenter>
</TabItem>
<TabItem>
<ContentPresenter Content=CustomerOrdersViewModel>
<ContentPresenter Content="{Binding CurrentPageViewModel}" />
</ContentPresenter>
</TabItem>
</TabControl>
Because the TabControl will auto-generate a ContentPresenter to wrap the .Content for each TabItem, you don't need this template at all.
But what it sounds like what you actually want is to bind the SelectedItem property of the TabControl
<TabControl ItemsSource="{Binding PageViewModels}"
SelectedItem="{Binding CurrentPageViewModel}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
We don't generally use a ContentPresenter outside of a ControlTemplate and certainly not like you are doing in your DataTemplate... the use of the ContentPresenter will make the WPF Framework search for a DataTemplate that matches the type of the Content, so in your case, you'll end up with an endless loop. Instead, you should put your associated view in the DataTemplate:
<DataTemplate DataType="{x:Type YourDataXamlPrefix:CurrentPageViewModel}">
<YourUiXamlPrefix:YourView DataContext="{Binding CurrentPageViewModel}" />
</DataTemplate>
In this way, when the Framework comes across your view model class in the collection, it will find this DataTemplate and render the relevant view.

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 TabControl MVVM ViewModel get instantiated every time i toggle between tabs [duplicate]

This question already has answers here:
Stop TabControl from recreating its children
(4 answers)
WPF TabControl - Preventing Unload on Tab Change?
(3 answers)
Closed 8 years ago.
I wrote some WPF application with MVVM pattern that holds a TabControl bound to collection of "TabViewModelItem".
The main window XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:XcomSavesGameEditor.ViewModel"
x:Class="XcomSavesGameEditor.MainWindow"
xmlns:Views="clr-namespace:XcomSavesGameEditor.View"
Title="X-COM Saved Game Editor" Height="650" Width="850" Background="#FF1B0000">
<Window.DataContext>
<ViewModel:TabsManagerViewModel/>
</Window.DataContext>
<Grid>
... (some not relevant code removed for clearity of question) ...
<TabControl x:Name="myTabs" Background="Black" Margin="0,25,0,0" BorderThickness="0,0,0,0" BorderBrush="Black" ItemsSource="{Binding Tabs}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModel:Tab0a_FileSaveData_ViewModel}">
<Views:Tab0a_FileSaveData_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab0b_Summary_ViewModel}">
<Views:Tab0b_Summary_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab1_Research_ViewModel}">
<Views:Tab1_Research_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab2_Engineering_ViewModel}">
<Views:Tab2_Engineering_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab3_Barracks_ViewModel}">
<Views:Tab3_Barracks_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab4_Hangar_ViewModel}">
<Views:Tab4_Hangar_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab5_SituationRoom_ViewModel}">
<Views:Tab5_SituationRoom_View />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<Grid Margin="0">
<Border Margin="0,0,0,0"
Background="Black"
BorderBrush="Black"
BorderThickness="0,0,0,0" Padding="0,0,0,0">
<StackPanel Orientation="Horizontal"
Margin="0,0,0,0">
<Image Name ="tabImage" Source="{Binding TabImage_Disabled}" />
</StackPanel>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter TargetName="tabImage" Property="Source" Value="{Binding TabImage_Enabled}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="{Binding TabImage_Background}"/>
</Grid.Background>
<UniformGrid>
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TabContents}" />
</UniformGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
and the ViewModel that hold collection of tab is code:
public sealed class TabsManagerViewModel : ViewModelBase
{
private ObservableCollection<TabViewModelItem> _tabs;
public ObservableCollection<TabViewModelItem> Tabs
{
get { return _tabs; }
set
{
_tabs = value;
RaisePropertyChanged("Tabs");
}
}
public TabsManagerViewModel()
{
Tabs = new ObservableCollection<TabViewModelItem>();
Tabs.Add(new TabViewModelItem { TabName = "File_Save_Data", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.SaveFileData_Tab], TabContents = new Tab0a_FileSaveData_ViewModel() });
Tabs.Add(new TabViewModelItem { TabName = "Summary", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.Summary_Tab], TabContents = new Tab0b_Summary_ViewModel() });
... (rest of code removed for clearity of question)
}
}
So basically it's tab control that is bound to a collection of "TabViews".
and based of the object data type it's showing View1 or View2.
Note: View1 & View 2 are UserControls, each bound to it's own ViewModel.
This concept works fine.
Now where is the problem you my ask ?
My problem is:
EVERY time I click on another tab & then return to same tab, I get that specifc tab ViewModel constructor called again, where as I would expect the ViewModel object would remain.
This is problem, because it cause me to lose any modification made on that page, when I toggle between tabs. and since the ctor is called everytime, over & over, I can't even use the VIewModel to store this information.
My questions are:
1) Is there any way I can prevent the TabControl to dispose ViewModel objects when tab is inactive ? Meaning to pre-create all ViewModel's object & not dispose them when hidden ?
2) What "workarounds" using this concept exist, that allow me to store "visual tree" of the given tab, so if i navigate away from it & then re-open it, it will store all information on it (such as selected check boxes, written text, etc.)
Would appreciate any help on matter.
regards,
Idan
Solution to problem is extending TabControl and replacing default behaviour so it will not unload old tabs.
The final solution (with include's both control & control template) is #
Stop TabControl from recreating its children
Thanks for Shoe for pointing me in right direction that lead to final solution :)
I have the similar problem and i came up with this solution
void OnAddWorkSpace(object Sender, EventArgs e)
{
WorkspaceViewModel loclViewModel = (e as WorkSpaceEventArgs).ViewModel;
DataTemplate loclTemplate = (DataTemplate)Resources[new DataTemplateKey(loclViewModel.GetType())];
if (loclTemplate != null)
{
DXTabItem loclTabItem = new DXTabItem();
loclTabItem.Content = loclTemplate.LoadContent();
(loclTabItem.Content as DependencyObject).SetValue(DataContextProperty, loclViewModel);
loclTabItem.HeaderTemplate = (DataTemplate)FindResource("WorkspaceItemTemplate");
loclTabItem.Header = (loclTabItem.Content as DependencyObject).GetValue(DataContextProperty);
MainContentTabs.Items.Add(loclTabItem);
}
}
I have created an event handler in my ViewModel and my View subscribes to that. My ViewModel objects get added to a collection. Now, whenever a ViewModel is added, my MainViewModel will call this even handler.
Here, we need to find the DataTemplate that would have been defined for the DataType of the ViewModel that we are adding. Once we get hold of that, we can create a tab item and then load the content from the datatemplate.
Since i am using DevExpress TabControl, i have created DXTabItem. A TabItem should also work the same.

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>

How to properly bind a ListBoxItem in WPF?

I have a listbox and I want to iterate over a collection of Bars in my Foo-object.
<ListBox DataContext="{Binding Path=Foo.Bars}" >
<ListBox.Items>
<ListBoxItem>
<ContentControl DataContext="{Binding Path=.}" />
</ListBoxItem>
</ListBox.Items>
</ListBox>
This is the datatemplate I want to use.
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
If I snoop (--> examine by using the tool Snoop) my application, I notice that the entire collection of Bars is bound to the ContentControl, in stead of just 1.
How can I properly bind so the iteration over the collection goes fine?
You can just set the DataTemplate, and WPF does all the work. Set the ItemsSource to a list of Bar items, and then define a DataTemplate for Bar items.
<ListBox ItemsSource="{Binding Path=Foo.Bars}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could also set the ItemsTemplate directly by using <ListBox.ItemTemplate> instead of <ListBox.Resources>
See Data Binding Overview at MSDN.
First add your namespace to the Window element (Intellisense) :
xmlns:local="clr-namespace:yourenamespace"
Then the following XAML ( in Window.Resources is a clean way to do it ) :
<Window.Resources>
<ObjectDataProvider x:Key="DataProvider" ObjectType="{x:Type local:Foo}"/>
<DataTemplate x:Key="Template" >
<TextBlock Text="{Binding Bar}"/>
</DataTemplate>
</Window.Resources>
Place the Listbox :
<ListBox DataContext="{Binding Source={StaticResource DataProvider}}" ItemsSource="{Binding Bars}" ItemTemplate="DynamicResource Template" />
But, it depends on your code-behind object, you have to set a constructor to initialise public properties within your object which are ObservableCollection<> preferably (There is some restriction rules with object instance in XAML).

Resources