Creating a WPF user control with a child element - wpf

I try to create a "container" user control that can display UIElement objects as child in WPF. The usage of the control should look like this:
<general:DisplayContainer Title="Hello">
<TextBlock x:Name="AnyUIElement" />
</general:DisplayContainer>
where the TextBlock is just an example. So far I created the UserControl and bind the Title property like this:
<Grid x:Name="Grid_Main">
<Border x:Name="Border_Main" BorderThickness="2" Margin="10" CornerRadius="5" Padding="7" />
<TextBlock x:Name="TextBlock_Title" Background="{Binding Background, ElementName=Grid_Main}" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Path=Title, FallbackValue=Title}" Margin="20,2,0,0" Padding="3,0" />
</Grid>
The Codebehind file looks like this:
public partial class DisplayContainer : UserControl
{
public DisplayContainer()
{
InitializeComponent();
this.DataContext = this;
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title",typeof(string), typeof(DisplayContainer));
}
Now, how can I modify my control in a way, that i accepts a child element in the XAML-file, when I use the control? The child should be displayed through the Border_Main.Child Property.
Thanks in advance,
Frank

Just set the UserControl's Template property
<UserControl ...>
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<StackPanel>
<TextBlock Text="{Binding Title,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</UserControl.Template>
</UserControl>
and put the displayed element in the UserControl's Content:
<local:DisplayContainer Title="A Title">
<TextBlock Text="Hello"/>
</local:DisplayContainer>

Or you could define a DisplayContainer as a ContentControl without a .xaml file but with a ControlTemplate:
public partial class DisplayContainer : ContentControl
{
public DisplayContainer()
{
this.DataContext = this;
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(DisplayContainer));
}
XAML:
<Window x:Class="WpfApp1.MainWindow"
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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
<Style TargetType="local:DisplayContainer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DisplayContainer">
<Grid x:Name="Grid_Main">
<Border x:Name="Border_Main" BorderThickness="2" Margin="10" CornerRadius="5" Padding="7">
<ContentPresenter />
</Border>
<TextBlock x:Name="TextBlock_Title" Background="{Binding Background, ElementName=Grid_Main}"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Path=Title, RelativeSource={RelativeSource AncestorType=local:DisplayContainer}, FallbackValue=Title}" Margin="20,2,0,0" Padding="3,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<local:DisplayContainer Title="Hello">
<TextBlock Text="AnyUIElement..." />
</local:DisplayContainer>
</StackPanel>
</Window>

Or, yet another way.
Create a DependencyProperty 'Child' of type UIElement in DisplayContainer
Add a ContentPresenter to Border_Main that has it's content bound to the Child DependencyProperty.
Mark DisplayContainer with the ContentProperty attribute (value of "Child")
You can add many DPs if you needed to have different sections. Just add more ContentPresenters bound to the different DPs (Child, Header, Footer, etc..).
DisplayContainer.cs
[System.Windows.Markup.ContentProperty("Child")]
public partial class DisplayContainer : UserControl
{
public DisplayContainer()
{
InitializeComponent();
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(DisplayContainer));
public UIElement Child
{
get { return (UIElement)GetValue(ChildProperty); }
set { SetValue(ChildProperty, value); }
}
public static readonly DependencyProperty ChildProperty = DependencyProperty.Register("Child", typeof(UIElement), typeof(DisplayContainer));
}
DisplayContainer.xaml
<UserControl x:Class="WpfApp19.DisplayContainer"
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:local="clr-namespace:WpfApp19"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="Grid_Main">
<Border x:Name="Border_Main" BorderThickness="2" Margin="10" CornerRadius="5" Padding="7">
<ContentPresenter Content="{Binding Child, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Border>
<TextBlock x:Name="TextBlock_Title" Background="{Binding Background, ElementName=Grid_Main}" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Path=Title, FallbackValue=Title, RelativeSource={RelativeSource AncestorType=UserControl}}" Margin="20,2,0,0" Padding="3,0" />
</Grid>
</UserControl>
MainWindow.xaml
<Window x:Class="WpfApp19.MainWindow"
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:general="clr-namespace:WpfApp19"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<general:DisplayContainer Title="Hello">
<StackPanel>
<TextBlock Text="Test1" />
<TextBlock Text="Test2" />
</StackPanel>
<!-- Alternative way of setting Child - if you had more DPs (Header, Footer, etc..) you would have to set their content this way
<general:DisplayContainer.Child>
<TextBlock Text="AnyUIElement" />
</general:DisplayContainer.Child>
-->
</general:DisplayContainer>
</Grid>
</Window>

Related

Custom User Control not binding data [duplicate]

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Datacontext conflicts
(1 answer)
How to correctly bind to a dependency property of a usercontrol in a MVVM framework
(4 answers)
Closed 5 years ago.
I've created a user control that have a label and textbox.
i added two DependencyProperties (Text and Label) and bind them to textbox.text and label.content.
however, i'm not able to see the text of textbox.
in the main window, when i'm not binding to any element the label is shown but if i binding the element is not shown. the textbox not showing either way.
here's the xaml:
<UserControl x:Class="TestNewLabeltextbox.UserControl1"
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">
<StackPanel Orientation="Horizontal" Background="White" FlowDirection="RightToLeft">
<Label x:Name="lbl" Content="{Binding Label, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" HorizontalAlignment="Left" Background="blue">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal">
<Border Background="Blue" Width="200" BorderThickness="0,0,0,0">
<StackPanel Orientation="Horizontal">
<Viewbox StretchDirection="DownOnly" Stretch="Uniform">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" TextBlock.FontSize="14" TextBlock.Foreground="#FFFFFF" Margin="5">
<ContentPresenter.Effect>
<DropShadowEffect BlurRadius="0.0"
Color="#032A6B"
Direction="90"
Opacity="1"
ShadowDepth="1" />
</ContentPresenter.Effect>
</ContentPresenter>
</Viewbox>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Label.Style>
</Label>
<TextBox x:Name="txt" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="120" HorizontalAlignment="Right">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border CornerRadius="0,0,0,50" BorderBrush="Black" Background="White" BorderThickness="0">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" TextBlock.FontSize="14" TextBlock.Foreground="#FFFFFF" Margin="5">
<ContentPresenter.Effect>
<DropShadowEffect BlurRadius="0.0"
Color="#032A6B"
Direction="90"
Opacity="1"
ShadowDepth="1" />
</ContentPresenter.Effect>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
Here'sUserControl1.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
public string Label
{
get { return (string)this.GetValue(LabelProperty); }
set { this.SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
}
here's the window xaml + cs:
<Window x:Class="TestNewLabeltextbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:TestNewLabeltextbox"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical" Height="150">
<controls:UserControl1 Text="hello" Height="50" Label="{Binding Hello, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<controls:UserControl1 Text="hello" Height="50" Label="world" />
<Label BorderBrush="Black" BorderThickness="2" Width="100" Height="50" Content="{Binding Hello, Mode=TwoWay}"/>
</StackPanel>
</Grid>
public partial class MainWindow : Window
{
ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
vm.Hello = "555";
this.DataContext = vm;
}
}
viewmodel.cs
public class ViewModel : INotifyPropertyChanged
{
private string h = "Hello";
public string Hello
{
get
{
return h;
}
set
{
h = value;
NotifyPropertyChanged("Hello");
}
}
#region "PropertyChanged Event"
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Default Source of binding is DataContext. But your Label and Text dependency properties defined in the control rather than in view-model. Change binding of Label to
{Binding Path=Label, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}
and binding of TextBox to
{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
Please read about Mode and UpdateSourceTrigger properties of Binding. It seems that you don't know how they work. Mode=TwoWay, UpdateSourceTrigger=PropertyChanged doesn't make any sense for Content property.

WPF Data Binding From UserControl

I want to bind 'SomeText' from my UserControl, into the Content of my Label.
I currently have a UserControl which just displays my 'SomeText'. The XAML, and Code Behind file can be seen below.
<UserControl x:Class="TabHeader.UserControl1"
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="183" d:DesignWidth="235"
x:Name="uc">
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="57,102,0,0" Name="textBlock1" Content="{Binding Path=SomeText, ElementName=uc}" VerticalAlignment="Top" Width="86" />
</Grid>
</UserControl>
namespace TabHeader
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
private string someText;
public UserControl1()
{
this.SomeText = "23";
InitializeComponent();
}
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
}
}
}
}
I then have my main XAML page where I have, a Tab Control within a Grid. I'm using a Style to generate two Labels within the Columns Header. I am able to pull through the Header field, but I am unable to pull through the controls field.
<Window x:Class="TabHeader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:TabHeader"
Title="MainWindow" Height="350" Width="525" Name="Tabs">
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tabItemHeaderStyle" >
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=SomeText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=vw:UserControl1}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 1" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="UserControl11"></vw:UserControl1>
</TabItem>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 2" Name="tabItem2">
</TabItem>
</TabControl>
</Grid>
</Window>
Any assistance with this would be greatly appreciated.
Cheers.
Edit 1
For anyone interested added my working code below, where I have used the DependencyProperty.
MainWindow.xaml
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tab1ItemHeaderStyle">
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=UC1Figure, ElementName=uc1}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tab1ItemHeaderStyle}" Header="[Tab 1]" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="uc1"></vw:UserControl1>
</TabItem>
<TabControl>
</Grid>
UserControl1.xaml
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="69,128,0,0" Name="textBlock1" Content="{Binding Path=UC1Figure, ElementName=uc}" VerticalAlignment="Top" Width="100" />
<Button Name="updateSomeFigure" Content="Press Me" Click="updateSomeFigure_Click" Width="100" Height="100" Margin="69,12,66,71" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty SomeFigureProperty =
DependencyProperty.Register("UC1Figure", typeof(int), typeof(UserControl1));
public int UC1Figure
{
get { return (int)this.GetValue(SomeFigureProperty); }
set { this.SetValue(SomeFigureProperty, value); }
}
private void updateSomeFigure_Click(object sender, RoutedEventArgs e)
{
UC1Figure = UC1Figure + 1;
}
}
If you want to data bind a property to the UI of your UserControl, you have two options. The first is to implement the INotifyPropertyChanged Interface in your code behind. The second is to define DependencyPropertys instead of regular CLR properties. You can find out how to do that in the Dependency Properties Overview page on MSDN.
You might also want to read the Data Binding Overviewā€ˇ page on MSDN before you start data Binding.

Wpf contentControl

New programmer question:
I'm trying to create a simple navigation welcome page similiar to wht you whould see on an ipad.
My MainWindow has a titlebar (which wont change) and the rest will be a container of sorts that will show differnt things based on events.
So here is my question how do I bind the container (contentcontrol) to show other views ie show welcomeview originally and then if a user click on a button from the welcome view the content control shows the view they selected.
I currently has the Welcome Page as:
<UserControl x:Class="ContentControl.Views.WelcomeView"
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:vm="clr-namespace:ContentControl.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:WelcomeViewModel />
</UserControl.DataContext>
<Grid Background="red">
<Grid.RowDefinitions >
<RowDefinition Height="25*" />
<RowDefinition Height="50*"/>
<RowDefinition Height="25*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Green"/>
<DockPanel Grid.Row="1" HorizontalAlignment="Center" Background="White">
<Button Height="50" Width="50" Margin="5" Content="DASH" Command="{Binding ViewChangedCommand}" CommandParameter="Dashboard"/>
<Button Height="50" Width="50" Margin="5" Content="ROOM" Command="{Binding ViewChangedCommand}" CommandParameter="MeetingRoom"/>
<Button Height="50" Width="50" Margin="5" Content="SCREEN" Command="{Binding ViewChangedCommand}" CommandParameter="Screen" />
</DockPanel>
<Rectangle Grid.Row="2" Fill="Blue"/>
</Grid>
</UserControl>
The viewModel is as follows:
class WelcomeViewModel : BaseViewModel
{
private MainWindowViewModel _mainWindowVm;
private RelayCommand<string> _viewChangedCommand;
public ICommand ViewChangedCommand
{
get { return _viewChangedCommand ?? (_viewChangedCommand = new RelayCommand<string>(OnViewChanged)); }
}
public event EventHandler ViewChanged;
private void OnViewChanged(string view)
{
EventHandler handler = ViewChanged;
if (handler != null) handler(view, EventArgs.Empty);
}
public MainWindowViewModel MainWindowVm
{
get { return _mainWindowVm; }
set
{
_mainWindowVm = value;
OnPropertyChanged("MainViewModel");
}
}
public WelcomeViewModel()
{
MainWindowVm = new MainWindowViewModel();
ViewChanged += MainWindowVm.ViewChanged;
}
}
}
And I have my Mainwindow as follows:
<Window x:Class="ContentControl.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControl.ViewModels"
xmlns:views="clr-namespace:ContentControl.Views"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ScreenViewModel}">
<views:ScreenView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:WelcomeViewModel}">
<views:WelcomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MeetingRoomViewModel}">
<views:MeetingRoomView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
<views:DashboardView />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Label>This Is My Label</Label>
<ContentControl x:Name="MainPanel" Content="{Binding Content, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
MinHeight="200"
MinWidth="200"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Focusable="False">
</ContentControl>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
class MainWindowViewModel : BaseViewModel
{
private UserControl _content;
public UserControl Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("Content");
}
}
public void ViewChanged(object o, EventArgs e)
{
switch (o.ToString())
{
case "Welcome":
{
var welcome = new WelcomeView();
Content = welcome;
break;
}
case "MeetingRoom":
{
var meetingRoom = new MeetingRoomView();
Content= meetingRoom;
break;
}
case "Dashboard":
{
var dashboard = new DashboardView();
Content = dashboard;
break;
}
case "Screen":
{
var screen = new ScreenView();
Content = screen;
break;
}
}
MessageBox.Show(o.ToString());
}
public MainWindowViewModel()
{
}
}
}
How do I hook these Up to work with each other So I can show the WelcomeView as the content of the contentcontrol and then when I press a button have the ContentControl change to what was selected.
Again sorry I am new to MVVM and WPF and these binding are messing with my head.
The Buttons DASH, ROOM, SCREEN, should be on the MainWindow. The ViewChangedCommand should be on the MainWindowViewModel. And the content will be showed by dynamic templating.
If you want the buttons on the controls:
Ok so, then let's put the buttons on the controls, but the change content command should be in the MainWindowViewModel. To make a binding like this you should do something like this:
Command="{Binding Path=DataContext.ChangeContentCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
in your buttons

Accessing methods of a control that is in a Content Template

Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:
public class MyClass
{
public String Name { get; set; }
public String Description { get; set; }
}
I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:
MyControl.xaml:
<UserControl
x:Class="WpfApplication1.MyControl"
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"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="txtDescription"
Grid.Column="0"
Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />
<Button
x:Name="btnBold"
Grid.Column="1"
Content="Bold"
Click="btnBold_Click" />
</Grid>
</UserControl>
MyControl.xaml.cs:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));
public String Description
{
get { return GetValue(MyControl.DescriptionProperty) as String; }
set { SetValue(MyControl.DescriptionProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
private void btnBold_Click(object sender, RoutedEventArgs e)
{
ToggleBold();
}
public void ToggleBold()
{
if (txtDescription.FontWeight == FontWeights.Bold)
{
btnBold.Content = "Bold";
txtDescription.FontWeight = FontWeights.Normal;
}
else
{
btnBold.Content = "Unbold";
txtDescription.FontWeight = FontWeights.Bold;
}
}
}
In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<TabControl x:Name="tabItems">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
Description="{Binding Description}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyClass> myClasses = new List<MyClass>();
myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });
tabItems.ItemsSource = myClasses;
}
}
When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.
My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="tabItems" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
x:Name="myControl"
Description="{Binding Description}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
...
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass myClass = tabItems.SelectedItem as MyClass;
MyControl myControl;
///get the instance of myControl that is contained
///in the content template of tabItems for the
///myClass item
myControl.ToggleBold();
}
...
I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.
Any help would be hugely appreciated.
You can navigate the VisualTree to find the control you're looking for.
For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:
var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
if (myControl != null)
myControl.ToggleBold();

Elements are not shown with ItemsPanelTemplate

I just try to understand the concept of the ItemsPanelTemplate. For this i built a small sample solution.
I have a UserControl "MyListView" with the following Code.
MyListView.xaml:
<UserControl x:Class="WpfApplication2.MyListView"
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">
<UserControl.Resources>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderBrush="Black" BorderThickness="1" Margin="0" Padding="0" Width="100" Background="Gray">
<TextBlock Text="Text" HorizontalAlignment="Center" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ListView ItemsSource="{Binding Path=TreeItemChildren}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</UserControl>
In MyListView.cs I added a DependencyProperty to bind the data to that should be displayed:
public partial class MyListView : UserControl
{
public MyListView()
{
this.TreeItemChildren = new ObservableCollection<string>();
this.TreeItemChildren.Add("Text0");
this.TreeItemChildren.Add("Text1");
this.TreeItemChildren.Add("Text2");
InitializeComponent();
}
public ObservableCollection<string> TreeItemChildren
{
get { return (ObservableCollection<string>)GetValue(TreeItemChildrenProperty); }
set { SetValue(TreeItemChildrenProperty, value); }
}
// Using a DependencyProperty as the backing store for TreeItemChildren. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TreeItemChildrenProperty =
DependencyProperty.Register("TreeItemChildren", typeof(ObservableCollection<string>), typeof(MainWindow), new UIPropertyMetadata(null));
}
When I now try to use this UserControl in my MainWindow, there is no data to be displayed. What is the reason for that?
You have not bound any ListBoxItem properties in the control template, the Content is not displayed for that reason.
Usually you would replace this:
<TextBlock Text="Text" HorizontalAlignment="Center" />
with:
<ContentPresenter HorizontalAlignment="Center"/>
(If the templated control is a ContentControl the ContentPresenter binds to the Content related properties automatically)
Also the ListView.ItemsSource binds to a property on the DataContext (which isn't set and should not be set since it interferes with bindings on the instances of the control), change it to somehow target the UserControl (e.g. use ElementName or RelativeSource).
(There should be binding errors caused by the ItemsSource binding, learn how to debug them)

Resources