How to use ContentPresenter properly in custom WPF UserControl - wpf

I want do have a custom UserControl in WPF that basically only puts a caption TextBlock over the actual content (I call it 'AttributePanelItem' here.
However, in my current approach the TextBlock is not shown when I direclty assign the Content of the user control.
This is my current XAML for the custom UserControl:
<UserControl x:Class="Common.Controls.AttributePanelItem"
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"
Name="MyAttributePanelItem"
d:DesignHeight="100" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Caption, ElementName=MyAttributePanelItem}"/>
<ContentPresenter Grid.Row="1" Content="{Binding InputMask, ElementName=MyAttributePanelItem}" />
</Grid>
</UserControl>
Here the code behind:
public AttributePanelItem()
{
InitializeComponent();
}
public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(AttributePanelItem), new PropertyMetadata(string.Empty));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty InputMaskProperty = DependencyProperty.Register("InputMask", typeof(object), typeof(AttributePanelItem), new PropertyMetadata(null));
public object InputMask
{
get { return (object)GetValue(InputMaskProperty); }
set { SetValue(InputMaskProperty, value); }
}
This is my current XAML to use the custom UserControl:
<controls:AttributePanel>
<controls:AttributePanelItem Caption="This caption is shown">
<controls:AttributePanelItem.InputMask>
<TextBox Text="This is my input 1" />
</controls:AttributePanelItem.InputMask>
</controls:AttributePanelItem>
<controls:AttributePanelItem Caption="This caption is not shown">
<TextBox Text="This is my input 2" />
</controls:AttributePanelItem>
</controls:AttributePanel>
In my implementation I use the AttributePanelItem two times.
1. The first usage is working as expected however this is not my favorite.
2. The second usage is how I would like to have it. Unfortunately in this case the caption-TextBlock is not shown.
Would it be possible to make the second case work (with showing the caption TextBlock, but without having to use the )?
I assume I am using the ContentPresenter wrong. However I do not know what I need to change.
Could you please help?

Your use of ContentPresenter is correct, but it should be inside a ContentTemplate. You need to change the UserControl.ContentTemplate to do what you want:
<UserControl
x:Class="WpfApp1.AttributePanelItem"
x:Name="MyAttributePanelItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<UserControl.Style>
<Style TargetType="UserControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="UserControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Caption, ElementName=MyAttributePanelItem}" />
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Style>
</UserControl>
Now you'll be able to use your second usage. In fact, you can completely remove InputMask from your UserControl (unless you're using it for something else).

Related

How to bind a UserControl's property to a property?

I'd like to set a property of a re-defined UserControl (for example its background color) to a property of the class. For example.
If I define the background of a Button to a property (<Button x:Name="myButton" Background="{Binding ColorName}"/>), it works fine. However, if I do the same for a re-defined UserControl (<local:MyUserControl Background="{Binding Path=ColorName}"/>), it does not.
What's funny though, is that, if I do <local:MyUserControl Background="{Binding Background, ElementName=myButton}"/>, it works perfectly fine.
Could I have some help on that? I must be missing something.
Thanks!
EDIT
Here is all the code. The setting of the background color worked fine. What solved this was to set properly the MainWindow.DataContext and to remove the DataContext = this in MyUserControl.xaml.cs. Setting Color as a DependencyProperty is also useful to be able to change the Color setting in a later execution of the code.
Nonetheless, while removing DataContext=this in MyUserControl.xaml.cs,
the {Binding TextContent} does not work and needs to be replaced by {Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}.
MainWindow.xaml
<Window x:Class="BindingBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:BindingBug"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="0"
x:Name="myButton"/>
<c:MyUserControl Background="{Binding Background, ElementName=myButton}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="1"/>
<c:MyUserControl Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="2"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Media;
namespace BindingBug
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Color = Brushes.Red;
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(MainWindow));
public Brush Color
{
get
{
return (Brush)GetValue(ColorProperty);
}
set
{
SetValue(ColorProperty, value);
}
}
}
}
MyUserControl.xaml
<UserControl x:Class="BindingBug.MyUserControl"
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:c="clr-namespace:BindingBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="13"
Text="{Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
MyUserControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingBug
{
/// <summary>
/// Interaction logic for NumberDataHolder.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register("TextContent", typeof(string), typeof(MyUserControl));
public string TextContent
{
get
{
return (string)GetValue(TextContentProperty);
}
set
{
SetValue(TextContentProperty, value);
}
}
}
}
EDIT 2
I tried to acheive the same results without having to declare the whole Text="{Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}" inside TextBlock. So, following #KeithStein advice, I placed DataContext="{Binding RelativeSource={RelativeSource Self}}" inside MyUserControl and only kept Text="{Binding TextContent}"inside TextBlock. That, however cancels the effect of setting Background="{Binding Path=Color}" in MainWindow.xaml. Any idea why? Is there another possibility to set Background="{Binding Path=Color}" in MainWindow.xaml and to only keepText="{Binding TextContent}"inside TextBlock?
MainWindow.xaml
<Window x:Class="BindingBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:BindingBug"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="0"
x:Name="myButton"/>
<c:MyUserControl Background="{Binding Background, ElementName=myButton}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="1"/>
<c:MyUserControl Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="2"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Media;
namespace BindingBug
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Color = Brushes.Red;
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(MainWindow));
public Brush Color
{
get
{
return (Brush)GetValue(ColorProperty);
}
set
{
SetValue(ColorProperty, value);
}
}
}
}
MyUserControl.xaml
<UserControl x:Class="BindingBug.MyUserControl"
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:c="clr-namespace:BindingBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="13"
Text="{Binding TextContent}"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
MyUserControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingBug
{
/// <summary>
/// Interaction logic for NumberDataHolder.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register("TextContent", typeof(string), typeof(MyUserControl));
public string TextContent
{
get
{
return (string)GetValue(TextContentProperty);
}
set
{
SetValue(TextContentProperty, value);
}
}
}
}
This answer developed gradually through back and forth comments with OP. To summarize:
Use a Brush-type dependency property for your color. Brush because that is the type of the Background property that you want to bind to, and a dependency property so that updates of the property trigger any bindings to refresh.
When binding inside a Window or UserControl, you need to set DataContext, which is essentially the default sourced used by bindings.
For a Window, add DataContext="{Binding RelativeSource={RelativeSource Self}}" to the opening tag. This sets the default source for all controls contained within to the Window itself.
For a UserControl, add the following to the outer-most panel of said control: DataContext={Binding RelativeSource={RelativeSource AncestorType=UserControl}} (UserControl can be replaced with the name of your particular control, i.e. c:MyUserControl). This tells everything inside that root panel to use the UserControl as the default source. You can't use RelativeSource Self in this case, because then instances of the MyUserControl will bind to themselves when placed inside Windows, instead of inheriting the Window's DataContext.

Caliburn.Micro : How to bind a specific Item of Conductor.Collection.AllActive to a ContentControl

My goal is to have 4 different active ViewModels displayed in a grid on the ShellView. The issue is that I have not been able to figure out how to wire up a ContentControl to a specific Item in Items of the Conductor. How can his be done?
Here is a simplified version of what I and trying to do.
SolutionExplorer
ShellViewModel:
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
public ShellViewModel()
{
ActivateItem(new UC1ViewModel());
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
ShellView:
<Window x:Class="ContentControlTest.Views.ShellView"
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:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Title="ShellView" Height="450" Width="800"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="0" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC2ViewModel}" cal:View.Context="{Binding Items[1]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC3ViewModel}" cal:View.Context="{Binding Items[2]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC4ViewModel}" cal:View.Context="{Binding Items[3]}"/>
</ScrollViewer>
</Grid>
</Window>
For simplification each UserControl ViewModel and View are Identical:
UC#ViewModel:
namespace ContentControlTest.ViewModels
{
public class UC1ViewModel : Screen
{
private string id;
public string ID
{
get { return id; }
set
{
id = value;
NotifyOfPropertyChange(() => ID);
}
}
public UC1ViewModel()
{
ID = Guid.NewGuid().ToString();
}
}
}
UC#View:
<UserControl x:Class="ContentControlTest.Views.UC1View"
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:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel >
<TextBlock Text="{Binding DisplayName}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
</Border>
</UserControl>
For testing I have tried using an ItemControl and it works but doesn't give me exactly what I want.
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You need to create properties in your ShellViewModel something like UC1, UC2, UC3 etc. You then need to change your ShellView to bind to UC1 property.
<ContentControl x:Name="UC1" />
...
Caliburn Micro should do the plumbing for you.
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
// Modify to implement INotifyPropertyChanged event...
public UC1ViewModel UC1 { get; set }
public ShellViewModel()
{
UC1 = new UC1ViewModel();
ActivateItem(UC1);
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
The Caliburn concept of Context is used to map a view model to multiple views, usually through conventions and mapping namespaces. In this case, however, each of your view models maps to exactly one view. Hence you don't need to / should not provide a context.
Second, your view model binding cannot be resolved without exposing them as public props (as #Jack suggested). Ironically, the binding you used for Context is the right one for the view model binding.
Replacing
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
With
<ContentControl cal:View.Model="{Binding Items[0]}"/>
Should do the trick.
Given the number of items is fixed it's better to follow #Jack's approach and reference the view models in a strongly typed fashion. Rather than relying on their index in the items collection. You can use either:
<ContentControl cal:View.Model="{Binding UC1ViewModel}" />
Or
<ContentControl x:Name="UC1ViewModel" />
Which are synonymous.
As you noticed the Caliburn Conductor really shines when used in combination with ItemControl. You typically don't need to have strongly typed references to the each of the Items then. That doesn't mean you can't use the conductor as you did, you still enjoy all the benefits of the managed lifecycle.
In case anyone is having an issue implementing the [perfectly fine] accepted answer, here is a more in depth answer:
Your main window that contain both (or even more than two) of your User Controls must be inherited from Caliburn.Micro.Conductor<Screen>.Collection.AllActive;
Your User Controls must be inherited from Caliburn.Micro.Screen;
You must also keep naming conventions in mind. If you use MenuUC as the name of a ContentControl in your View, also create a property named MenuUC in your ViewModel;
Initialize your UserControl as I do in Constructor;
Now you can use ActivateItem(MenuUC) and DeactivateItem(MenuUC) everywhere in your code. Caliburn.Micro automatically detects which one you want to work with.
Example XAML View code:
<Window x:Class="YourProject.Views.YourView"
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"
mc:Ignorable="d"
Title="YourViewTitle" Width="900" Height="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Menu Side Bar -->
<ContentControl Grid.Row="0" Grid.Column="0" x:Name="MenuUC" />
<!-- Panel -->
<Border Grid.Column="1" Grid.RowSpan="2" BorderThickness="1,0,0,0" BorderBrush="#FF707070" >
<ContentControl x:Name="PanelUC" />
</Border>
</Grid>
</Window>
Example C# ViewModel code:
class YourViewModel : Conductor<Screen>.Collection.AllActive
{
// Menu Side Bar
private MenuUCViewModel _menuUC;
public MenuUCViewModel MenuUC
{
get { return _menuUC; }
set { _menuUC = value; NotifyOfPropertyChange(() => MenuUC); }
}
// Panel
private Screen _panelUC;
public Screen PanelUC
{
get { return _panelUC; }
set { _panelUC = value; NotifyOfPropertyChange(() => PanelUC); }
}
// Constructor
public YourViewModel()
{
MenuUC = new MenuUCViewModel();
ActivateItem(MenuUC);
PanelUC = new FirstPanelUCViewModel();
ActivateItem(PanelUC);
}
// Some method that changes PanelUC (previously FirstPanelUCViewModel) to SecondPanelUCViewModel
public void ChangePanels()
{
DeactivateItem(PanelUC);
PanelUC = new SecondPanelUCViewModel();
ActivateItem(PanelUC);
}
}
In the above example, ChangePanels() acts as a method to load new User Control into your ContentControl.
Also read this question, it might be help you further.

Creating a WPF user control with a child element

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>

It is possible to do 'simple' binding with a UserControl?

I have a user control in which I have some textblocks. I want to include this usercontrol into a listBox (or listview in case it causes any problems).
When I check the output windows, I see no binding exception, but I don't see anything in the textblock either.
Is there anyway to make this work ?
Thanks :
Here is the listBox I use for now :
<ListBox AllowDrop="True" Grid.Row="1"
Style="{StaticResource BaseListBox}" x:Name="LstEquipeDefaut">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<my:ucEquipe x:Name="ucEquipe" Grid.Row="1" Margin="5,0,5,2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is the Usercontrol :
<UserControl x:Class="ucEquipe"
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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="180" MinWidth="180" >
<Border Style="{StaticResource UControlBorder}">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="25" />
<RowDefinition Height="35" />
<RowDefinition Height="100" />
<RowDefinition Height="35" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<TextBox AllowDrop="True" x:Name="TxtChiefEquipe"
Style="{StaticResource BaseTextBox}"
Text="{Binding Mode=OneWay,Path=chefEquipe.NomComplet}"
Grid.Row="1" />
</Grid>
</Border>
Here is the objets I use :
Public Class Equipe
Public Property ID As Long = 0
Public Property Couleur As String = ""
Public Property Semaine As New Date(1900, 1, 1)
Public Property chefEquipe As Employe = Nothing
Public Property ListEquipeEmploye As New List(Of EquipeEmploye)
Public Property ListEquipeEquipement As New List(Of EquipeEquipement)
End Class
The objet Employe have a property called NomComplet. For now I manually added new objects in the listbox for testing.
Your Equipe class needs to implement INotifyPropertyChanged
private Employe _chefEquipe;
public Employe ChefEquipe
{
get { retun _chefEquipe; }
set
{
_chefEquipe = value;
NotifyPropertyChanged("ChefEquipe");
}
}
Sorry about the C#, I don't remember the VB syntax anymore =)

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