WPF implicit datatemplate not displaying view - c#

fellow programmers.
I'm trying to build my first WPF app with tabbed interface. I've decided to not rely on any MVVM framework, since I'm only starting my journey.
I've found quite a few tutorials, but can't make WPF to pick up correct view for viewmodels.
What I have is Shell view, with underlying DataContext with ObservableCollection of my "Tabs viewmodels":
Shell.Xaml
<Controls:MetroWindow x:Class="WpfApplication1.View.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Shell" Height="327" Width="667"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:v="clr-namespace:WpfApplication1.View"
xmlns:vm="clr-namespace:WpfApplication1.ViewModel"
>
<Grid>
<TabControl ItemsSource="{Binding Pages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="x:Type vm:FirstPageModel">
<v:FirstPageView />
</DataTemplate>
<DataTemplate DataType="x:Type vm:SecondPageModel">
<v:SecondPageView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
All I with - to get my views inside tab's of tabcontrol. But, no matter where I put my DataTemplate (window.resources, tabcontrol.resources, etc), I only get something like this:
As I can understand - somehow WPF doesn't see my views, but I don't understand why.
My "views" are simple UserControls, like this:
<UserControl x:Class="WpfApplication1.View.FirstPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Message}" Margin="36,131,47,127" />
</Grid>
and corresponding ViewModel is:
public class FirstPageModel:BaseViewModel
{
public override string DisplayName
{
get
{
return "First page";
}
}
public string Message { get { return "Hi from " + DisplayName; } }
}
Could somebody tell me what's wrong with my code?
Edit:
ShellViewModel:
public class ShellViewModel : BaseViewModel
{
public override string DisplayName
{
get
{
return "First";
}
}
private ObservableCollection<BaseViewModel> pages;
public ObservableCollection<BaseViewModel> Pages
{
get{
if(this.pages==null)
{
this.pages = new ObservableCollection<BaseViewModel>{
new FirstPageModel(),
new SecondPageModel()
};
}
return this.pages;
}
}
}
App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Shell sh = new Shell();
ShellViewModel shvm = new ShellViewModel();
sh.DataContext = shvm;
sh.Show();
}
}

The DataTemplate definition is incorrect. You must use curly braces for x:Type markup extension with DataType attribute value:
<TabControl ItemsSource="{Binding Pages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<!-- Use curly braces for x:Type markup extension -->
<DataTemplate DataType="{x:Type vm:FirstPageModel}">
<v:FirstPageView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondPageModel}">
<v:SecondPageView />
</DataTemplate>
</TabControl.Resources>
</TabControl>

Related

WPF: ContentTemplate of ContentControl is always null

I have a ContentControl bound to an instance of class X and a DataTemplate for X in the resource section of the Main Window.
<Window x:Class="DT1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DT1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby"/>
</StackPanel>
</Grid>
</Window>
In the button click handler in the MainWindow code behind I have a handler which tries to access the ContentTemplate of the ContentControl but this is always null:
using System.Windows;
namespace DT1
{
public class X
{
}
public partial class MainWindow : Window
{
public X MyX { get; set; } = new X();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var template = cont.ContentTemplate;
System.Diagnostics.Debug.WriteLine($"ContentTemplate: {template?.ToString() ?? "template is null"}");
}
}
}
What is the right way to access the visual items in the DataTemplate for X?
It is null because you are not setting any datatemplate explicitly. When you define a datatemplate without providing an x:key and providing a DataType using x:Type you are making use of what it is known as Implicit Datatemplates.
In order to get the datatemplate from codebehind you must assign it explicitly:
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}" x:Key="XTemplate">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby" ContentTemplate="{StaticResource XTemplate}"/>
</StackPanel>
</Grid>
Hope this helps!

WPF binding different UserControls in a DataTemplate of TabControl

As a new in WPF and MVVM light, I am struggling to apply the MVVM pattern in a TabControl. I will give you an example of what I am trying to achieve.
TabOne xaml and its view model
<UserControl x:Class="TestTabControl.TabOne"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab one ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabOne ViewModel
class TabOne : ViewModelBase
{
public string TabName
{
get
{
return "TabOne";
}
}
}
TabTwo xaml and its viewmodel
<UserControl x:Class="TestTabControl.TabTwo"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab two ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabTwo ViewModel
class TabTwo : ViewModelBase
{
public string TabName
{
get
{
return "TabTwo";
}
}
}
and finally the MainWindow xaml and its viewmodel
<Window x:Class="TestTabControl.MainWindow"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
Title="Test Tab Control" MinWidth="500" Width="1000" Height="800">
<TabControl ItemsSource="{Binding TabViewModels}" >
<TabControl.ItemTemplate >
<!-- header template -->
<DataTemplate>
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
?????????
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
//MainWindow ViewModel
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<ViewModelBase> _tabViewModels;
public MainWindowViewModel()
{
_tabViewModels = new ObservableCollection<ViewModelBase>();
TabViewModels.Add(new TabOne());
TabViewModels.Add(new TabTwo());
}
public ObservableCollection<ViewModelBase> TabViewModels
{
get
{
return _tabViewModels;
}
set // is that part right?
{
_tabViewModels = value;
RaisePropertyChanged(() => TabViewModels);
}
}
}
What am I supposed to write in the DataTemplate? Can I pass both usercontrols for TabOne and TabTwo in this DataTemplate in order to get the view for each tab I click? Or do I need to write another DataTemplate?
You may already knew the answer by now. But for the benefits of other people, what you need to do is:
<Grid Margin="10">
<Grid.Resources>
<DataTemplate DataType="{x:Type local:TabOne}">
<local:UserControlOne/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TabTwo}">
<local:UserControlTwo/>
</DataTemplate>
</Grid.Resources>
<TabControl Margin="10"
ItemsSource="{Binding TabViewModels}">
</TabControl>
</Grid>
Please note that, your UserControl for TabOne ViewModel is also named TabOne.
I changed it to UserControlOne. Same applies to UserControlTwo.

MVVM: Frame navigation within page

My app is utilizing a SplitView which has a Frame as it's content. I can't seem to figure out how to use the buttons in my split view to change pages in the frame. Right now i'm trying to bind the SourcePageType to my view model, but that doesn't work. Here is my setup.
Frame
<SplitView.Content>
<Frame x:Name="frame" SourcePageType="{Binding FrameSource}">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo/>
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
</SplitView.Content>
View Model
private string frameSource;
public string FrameSource
{
get { return frameSource; }
set
{
frameSource = value;
RaisePropertyChanged("FrameSource");
}
}
private RelayCommand<string> navCommand;
public RelayCommand<string> NavCommand
{
get
{
navCommand = new RelayCommand<string>(ExecuteNav);
return navCommand;
}
}
public void ExecuteNav(string page)
{
FrameSource = page;
}
I'm using MVVM Light for my framework. What is the best way to do this?
I also have been struggling with this using mmvm light and came up with this method which I use in the main window with a content control bound to the selected viewmodel I want to show. It may be somewhat overkill, but it works and its not too difficult to maintain.
In the main page view model I create a menu object:
private void constructMenu()
{
MenuMessages = new ObservableCollection<MenuMessage>();
MenuMessages.Add(new MenuMessage
{
menutext = "FirstPage",
isactive = true,
newWindow = false,
viewModelName = "FirstPageViewModel"
});
MenuMessages.Add(new MenuMessage
{
menutext = "2page",
isactive = true,
newWindow = false,
viewModelName = "2pageViewModel"
});
I have the following INotifyable properties:
public MenuMessage selectedmenuitem
public ObservableCollection<MenuMessage> MenuMessages
public Object selectedViewModel
in addition each view model that I use is an INotifyable propery
public FirstPageViewModel firstpageviewmodel;
public 2PageViewModel firstpageviewmodel;
My main page xaml looks like this:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Myproj"
xmlns:Views="clr-namespace:Myproj.Views"
xmlns:vm="clr-namespace:Myproj.ViewModel"
x:Class="Myproj.MainWindow" mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstPageViewModel}">
<Views:FirstPageView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:2PageViewModel}">
<Views2pageView/>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Top" >
<ListView ItemsSource="{Binding MenuMessages}" SelectedItem="{Binding selectedmenuitem}" >
<ListView.ItemsPanel>
<ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/></ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type MenuItem}" >
<TextBlock Text="{Binding menutext}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl Content="{Binding selectedVM}" ></ContentControl>
</DockPanel>
In the viewmodel I call the following method after the RaisePropertyChanged of the selectedmenuitem setter:
private void switchviewmodel()
{
switch (selectedmenuitem.viewModelName)
{
case "FirstPageViewModel":
selectedVM = irstpageviewmodel;
break;
case "2PageViewModel":
selectedVM = 2pageviewmodel;
break;
}
}

WPF: Can't access the view. Datagrid is not refreshing with data in tabs

I'm following this How to create tab-able content in WPF/C#? but I want each tab to show a datagrid. The datagrid doesn't show and also doesn't show the data. When I step into the code, I do see 0,1 being set.
MainWindow.xaml
<Window x:Class="MVVMDataInstances.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewModel="clr-namespace:MVVMDataInstances"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}"/>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
</Window>
MainWindowViewModel.cs
public ObservableCollection<ChildViewModel> Items { get; private set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ChildViewModel> {new ChildViewModel(0), new ChildViewModel(1)};
}
ChildView.xaml
<UserControl x:Class="MVVMDataInstances.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModel="clr-namespace:MVVMDataInstances"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<ViewModel:ChildViewModel/>
</UserControl.DataContext>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Grid, Mode=TwoWay}" />
</UserControl>
ChildViewModel.cs
public class ChildViewModel : ViewModelBase
{
private ObservableCollection<ChildModel> _grid;
public ObservableCollection<ChildModel> Grid
{
get { return _grid; }
private set
{
_grid = value;
OnPropertyChanged("Grid");
}
}
public ChildModel Data { get; set; }
public ChildViewModel()
{
}
public ChildViewModel(int tabNumber)
{
Data = new ChildModel {A = tabNumber.ToString(CultureInfo.InvariantCulture)};
Grid = new ObservableCollection<ChildModel> {Data};
}
}
ChildModel.cs
public class ChildModel
{
public string A { get; set; }
public string B { get; set; }
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to see one grid per tab. The entry on the first tab has a value of 0 for property A. The entry of the second tab has a value of 1 for the property B.
I see that when OnPropertyChanged is called PropertyChanged is null.
I can access the datagrid if I have this in MainWindow.xaml
<Grid>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="True"
ItemsSource="{Binding Grid}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
But OnPropertyChanged is always null for this and I don't see the grid
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:ChildView />
</DataTemplate>
Your DataTemplate is empty. From your question, you probably want to do something along these lines:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" TargetType="{x:Type viewModel:ChildViewModel}">
<DataGrid AutoGenerateColumns="False" . . .>
<DataGrid.Columns>
<!-- Your column definitions here -->
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
This DataTemplate tells the ChildViewModel.cs that it's appearance is View.xaml :
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:View />
</DataTemplate>
As a result behind the scenes it also sets each View's DataContext to an instance of ChildViewModel.
I follow this MVVM: ViewModel inheritance to have ViewModel Inheritance. The reason PropertyChanged was null because the constructor was initialized twice. First time with an integer, and then second time with the InitializedComponent. If I comment out the InitializedComponent, the grid was never initialized. The answer to the other stackoverflow question makes everything cleaner with view model inheritance.

WinRT MVVM-Light ComboBox in ListView with different ItemsSources

I'm working on a WinRT application where i have a Listview with a ComboBox.
The Listview has a particular ObservableCollection as Itemssource, The ComboBox Should have another ObservableCollection as ItemsSource because i should be able to dynamicaly change the contents of the ComboBox.
I'm using the MVVM-Light framework, The ObservableCollections are filled in the ViewModel and displayed through databinding.
I'll give you an example Xaml code:
<Page x:Class="MvvmLight2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And Corresponding ViewModel:
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
CollectionOne = new ObservableCollection<ClassOne>();
for (int i = 0; i < 4; i++)
{
var temp = new ClassOne()
{
StringOne = "String " + i.ToString()
};
CollectionOne.Add(temp);
}
CollectionTwo = new ObservableCollection<ClassTwo>();
CollectionTwo.Add(new ClassTwo("ADV"));
CollectionTwo.Add(new ClassTwo("Wettelijk"));
}
private ObservableCollection<ClassOne> _collectionOne;
public ObservableCollection<ClassOne> CollectionOne
{
get { return _collectionOne; }
set
{
if (_collectionOne == value)
{
return;
}
_collectionOne = value;
RaisePropertyChanged(() => CollectionOne);
}
}
private ObservableCollection<ClassTwo> _collectionTwo;
public ObservableCollection<ClassTwo> CollectionTwo
{
get { return _collectionTwo; }
set
{
if (_collectionTwo == value)
{
return;
}
_collectionTwo = value;
RaisePropertyChanged(() => CollectionTwo);
}
}
}
In ClassOne and ClassTwo are for the example just one property in each class with a string.
Both collections have to remain seperate because they can be different in length when randomly filled.
EDIT
#Josh I followed your instructions but it still doesn't seem to work, Here are my adjustments:
<Page x:Class="MvvmLight2.MainPage"
x:Name="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Since you are using the ViewModel Locator to set your datacontext you can reuse this to find the property CollectionTwo.
Your binding would look like this:
<ComboBox ItemsSource="{Binding Path=Main.CollectionTwo, Source={StaticResource Locator}}" />
You need to move up one level in the datacontext to search the view model instead of the item that is bound at the list view level using RelativeSource:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Page}}, Path=CollectionTwo}" />
and for WinRT situations, use a control name:
ElementName=MyControl
instead of searching by AncestorType and give the page a name of 'MyControl'. It would then look like
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=DataContext.CollectionTwo}" />
and your Page would look like
<Page x:Name="MyControl"
The ComboBox binding is relative to the Binding of the ListItem. So it searches for CollectionTwo as a property of ClassOne. Either look at the RelativeSource to bind to or move the CollectionTwo to class ClassOne. That way, you can easily build up different lists for each ListViewItem.

Categories

Resources