WPF: ContentTemplate of ContentControl is always null - c#

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!

Related

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.

WPF implicit datatemplate not displaying view

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>

How to use binding in a TabItem with DataTemplate in WPF?

I'm trying to do something basic (I think!), but I've got a problem. I have got a TabControl and 3 TabItems. The 2 first items must be untouched, and I want to apply a dataTemplate on the third tabItem, so I used a DataTemplateSelector. This is OK, it works. But then, I want to fill data in the third tabItem with my datamodel. Binding is not working because my DataContext is always "null" in the tabItem. How can I set the DataContext in the tabItem created by the dataTemplate ? Here is my code :
XAML :
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="910" Width="1200">
<Window.Resources>
<DataTemplate x:Key="configurationTemplate" DataType="{x:Type local:ConfigurationDatamodel}">
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
</DataTemplate>
<local:TabItemTemplateSelector ConfigurationTemplate="{StaticResource configurationTemplate}" x:Key="tabItemTemplateSelector"/>
</Window.Resources>
<Grid>
<TabControl Height="800" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tabControl1" VerticalAlignment="Top" ContentTemplateSelector="{StaticResource tabItemTemplateSelector}">
<TabItem Header="TabItem1" Name="tabItem1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem2" Name="tabItem2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem3" Name="tabItem3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</TabItem>
</TabControl>
</Grid>
</Window>
UserControl of my DataTemplate :
<UserControl x:Class="WpfApplication1.ConfigurationTemplateUC"
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="800" d:DesignWidth="1000">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Label Content="File name : " Height="28" HorizontalAlignment="Left" Margin="50,43,0,0" Name="label1" VerticalAlignment="Top" FontSize="16"/>
<Label Content="{Binding Path=File}" FontSize="16" Height="28" HorizontalAlignment="Left" Margin="160,43,0,0" Name="label2" VerticalAlignment="Top" Width="324" />
</Grid>
</UserControl>
Datamodel :
public class ConfigurationDatamodel
{
private string file;
public string File
{
get { return this.file; }
set
{
this.file= value;
}
}
public ConfigurationDatamodel()
{}
public ConfigurationDatamodel(string file)
{
this.file= file;
}
}
Code-behind :
public MainWindow()
{
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
InitializeComponent();
tabItem3.DataContext = dt1;
}
There is no binding errors in the console, but the label containing the filename is always empty. The DataContext of the UserControl "ConfigurationTemplateUC" is always "null".
Any thoughts?
EDIT
If I set the DataContext in the contructor of the UserControl, it works :
public ConfigurationTemplateUC()
{
InitializeComponent();
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
this.DataContext = dt1;
}
How can I set this dataContext with the DataTemplate ?
In your DataTemplate, Remove the DataContext binding from
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
...leaving only
<local:ConfigurationTemplateUC />
UI in a DataTemplate automatically gets the bound data as its DataContext, so in your case you override the correct DataTemplate with a path to something that can't be found.
EDIT:
Also, you don't need a DataTemplateSelector to do this. You can remove the selector and instead simply use TabItem.ContentTemplate:
<TabItem Header="TabItem3" Name="tabItem3" ContentTemplate="{StaticResource configurationTemplate}" >
...

create a ListBox inside an UserControl and bind objects

I want to create a ListBox inside a UserContorl, and than, using that userControl to show and "manage" that list in many pages.
For example i got a list of trucks, each object truck has some property like the name, the id...
Now i create my own UserControl
<UserControl
x:Class="Crud.View.ListboxInUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Crud.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Name="myUserControl"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<ListBox x:Name="aName" ItemsSource="{Binding ??}">
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</ListBox>
</Grid>
How can i bind the items in the code behind?
And how can i manage the "click" on the list?
In a Page.xaml i want to write something like
<LUC:ListboxInUserControl x:Name="MyListbox DataContext="{Binding}"/>
and in the code behind
private ObservableCollection<Truck> TestList { get; set; }
...
TestList = await TruckService.GetAll(); //a method to get the list
MyListbox.MyItemsSource = TestList;
Add listbox to your UserControl,
<ListBox x:Name="aName" SelectionChanged="aName_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Add event handler for getting selection changed and public property of listbox to bind objects in user control code behind,
public event EventHandler<EventArgs> SelectionChangedEvent;
public ListBoxInUserControl()
{
this.InitializeComponent();
}
private void aName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectionChangedEvent(sender, new EventArgs());
}
private ListBox myVar;
public ListBox MyProperty
{
get { return aName; }
set { aName = value; }
}
Then you can add this usercontrol in your xaml,
<local:ListBoxInUserControl x:Name="uc_ListBoxInUserControl" SelectionChangedEvent="uc_ListBoxInUserControl_SelectionChangedEvent"> </local:ListBoxInUserControl>
In code behind you can bind data ,
uc_ListBoxInUserControl.MyProperty.ItemsSource = TestList;
and access selection changed event,
private void uc_ListBoxInUserControl_SelectionChangedEvent(object sender, EventArgs e)
{
}

Visibility Binding using DependencyProperty

I've some simple code below that uses a ToggleButton.IsChecked property to set the Visibility of a TextBlock. It works fine. Since this doesn't quite fit in with my program's structure, I'm trying to bind the visibility of another TextBlock to a DependencyProperty of "this". It compiles fine, but it produces no effect. I'm doing something wrong, just not sure what.
XAML
<Window x:Class="ToggleButtonTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="200" Height="100">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<ToggleButton x:Name="toggleButton" Content="Toggle"
IsChecked="True" Checked="toggleButton_Checked"/>
<TextBlock Text="Some Text"
Visibility="{Binding IsChecked,
ElementName=toggleButton,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="More Text"
Visibility="{Binding ShowMoreText,
ElementName=this,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
C#
using System.Windows;
namespace ToggleButtonTest
{
public partial class MainWindow : Window
{
static MainWindow()
{
FrameworkPropertyMetadata meta =
new FrameworkPropertyMetadata(true,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
ShowMoreTextProperty =
DependencyProperty.Register("ShowMoreText",
typeof(bool), typeof(MainWindow), meta);
}
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty ShowMoreTextProperty;
public bool ShowMoreText
{
get
{
return (bool)GetValue(ShowMoreTextProperty);
}
set
{
SetValue(ShowMoreTextProperty, value);
}
}
private void toggleButton_Checked(object sender, RoutedEventArgs e)
{
ShowMoreText = toggleButton.IsChecked.Value;
}
}
}
Edit:
Having had this answered, I want to post my working code...
XAML
<Window x:Class="ToggleButtonTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="200" Height="100"
Name="thisWindow">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<ToggleButton x:Name="toggleButton"
Content="Toggle"
IsChecked="{Binding Path=ShowMoreText, ElementName=thisWindow}"/>
<TextBlock Text="More Text"
Visibility="{Binding Path=ShowMoreText,
ElementName=thisWindow,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
C#
using System.Windows;
namespace ToggleButtonTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty ShowMoreTextProperty =
DependencyProperty.Register("ShowMoreText", typeof(bool),
typeof(MainWindow), new FrameworkPropertyMetadata(true,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public bool ShowMoreText
{
get
{
return (bool)GetValue(ShowMoreTextProperty);
}
set
{
SetValue(ShowMoreTextProperty, value);
}
}
}
}
ElementName must really be an element name. this doesn't fly. Fortunately, you do have an element of type MainWindow here with a ShowMoreText property: the root Window element.
Give the Window a name and use that as ElementName, as below:
<Window x:Class="ToggleButtonTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="200" Height="100"
x:Name="thisWindow">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<ToggleButton x:Name="toggleButton" Content="Toggle"
IsChecked="True" Checked="toggleButton_Checked"/>
<TextBlock Text="Some Text"
Visibility="{Binding IsChecked,
ElementName=toggleButton,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="More Text"
Visibility="{Binding ShowMoreText,
ElementName=thisWindow,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
Note that you can do the same using RelativeSource Self, but I prefer the method above.
The way you have it set up currently won't ever set ShowMoreText to false. The Checked handler will only be called when the ToggleButton's IsChecked changes from false to true. To also go the other way you need a handler for Unchecked as well. The best way to handle this situation would be to instead set a Binding on the ToggleButton that will do both without any event handlers (using Jay's changes):
IsChecked="{Binding Path=ShowMoreText, ElementName=thisWindow}"
Give your Window a name and set the ElementName to that name, instead of using "this".

Categories

Resources