Aligning Text with a ContentControl using the HorizontalContentAlignment property - wpf

I am attempting to apply a "text alignment" to a ContentControl. Since the ContentControl does not have a horizontal or vertical text alignment property like the TextBlock, I am attempting to use the ContentControl's HorizontalContentAlignment property.
My problem is that I can't get it to work with a ContentControl itself.
In my example, I have a content control displaying "hello world" and a button displaying "change it".
When I click the button, I set the HorizontalContentAlignment on the content control and on the button. The button's content changes, but the content control's content does not.
Here is my XAML code:
<Window x:Class="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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl x:Name="ctrl" Width="525">
Hello World!
</ContentControl>
<Button x:Name="btn" Grid.Row="1" Content="Change It" Click="btn_Click"/>
</Grid>
</Window>
And here is my VB.NET code for the button click event:
Class MainWindow
Private Sub btn_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
If (ctrl.HorizontalContentAlignment = HorizontalAlignment.Left) Then
ctrl.HorizontalContentAlignment = HorizontalAlignment.Right
btn.HorizontalContentAlignment = Windows.HorizontalAlignment.Right
Else
ctrl.HorizontalContentAlignment = HorizontalAlignment.Left
btn.HorizontalContentAlignment = Windows.HorizontalAlignment.Left
End If
ctrl.UpdateLayout()
End Sub
End Class
I am unable to replace my content controls with text blocks for various reasons, but I still need to be able to align the content.
EDIT:
While Narohi work around suggestion works, I am still confused about why the content control's HorizontalContentAlignment property doesn't align the content.
I tried a Label control (which inherits from ContentControl) and it's HorizontalContentAlignment property properly aligns the content.
(Edit again: I am no longer confused about this, it seems that the HorizontalContentAlignment isn't utilized properly in all cases.)
Here is my updated XAML code:
<Window x:Class="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">
<Window.Resources>
<ControlTemplate x:Key="AlignmentAwareControl" TargetType="ContentControl">
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl x:Name="ctrlTxt" Grid.Row="0"
Template="{StaticResource AlignmentAwareControl}"
HorizontalContentAlignment="Center" Padding="0">Hello World Content Control!</ContentControl>
<Label x:Name="ctrl" Grid.Row="1" HorizontalContentAlignment="Center" Padding="0">Hello World Label!</Label>
<ContentControl x:Name="ctrlImg" Grid.Row="2"
Template="{StaticResource AlignmentAwareControl}"
HorizontalContentAlignment="Center">
<Image Source="C:\Users\Frinavale\Pictures\Business_Woman.jpg"/>
</ContentControl>
<Button x:Name="btn" Grid.Row="3" Content="Change It" Click="btn_Click"/>
</Grid>
</Window>
Here is my updated VB.NET code:
Class MainWindow
Private Sub btn_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
If (ctrl.HorizontalContentAlignment = HorizontalAlignment.Left) Then
ctrlImg.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Right)
ctrlTxt.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Right)
ctrl.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Right)
btn.HorizontalContentAlignment = Windows.HorizontalAlignment.Right
Else
ctrlImg.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Left)
ctrlTxt.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Left)
ctrl.SetValue(ContentControl.HorizontalContentAlignmentProperty, Windows.HorizontalAlignment.Left)
btn.HorizontalContentAlignment = Windows.HorizontalAlignment.Left
End If
ctrl.UpdateLayout()
End Sub
End Class
I'm looking forward to your advice,
-Frinny

Opening up the default control template for ContentControl in Blend reveals why your original approach did not work.
<ControlTemplate TargetType="{x:Type ContentControl}">
<ContentPresenter/>
</ControlTemplate>
The default template does nothing with the HorizontalContentAlignment property which it inherited from Control. Juxtapose this with Label's default template.
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
As we can see, the Label actually binds to the HorizontalContentAlignment. WPF controls are lookless, so there is never a guarantee that a property will be respected by the current ControlTemplate. I would speculate that the WPF designers didn't respect the HorizontalContentAlignment property because people usually place content within that is independent of the ContentControl's properties or perhaps they assumed if someone was going to use such a generic control they would provide their own template, such as...
<ContentControl x:Name="ctrl" Width="525">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid>
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Grid>
</ControlTemplate>
</ContentControl.Template>
Hello World!
</ContentControl>

Related

XAML: How to bind TabControl nested element to Attached Property of TabItem

I have been searching for a "pure" XAML solution for this problem but just cannot find it.
My goal would be to only create an Attached Property in code behind but the rest should be XAML only without creating a Custom Control or User Control. But I'm not sure whether this is possible at all and if, how to make the connection between a nested element inside the TabControl template and an Attached Property set in a TabItem
I'd have a boilerplate Attached Property of string with [AttachedPropertyBrowsableForType(typeof(TabItem))] and/or [AttachedPropertyBrowsableForType(typeof(TabControl))] inside my MainWindow class and the following XAML
<Window x:Class="AP_Test.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:AP_Test"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800">
<Window.Resources>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="ColumnDefinition0"/>
<ColumnDefinition Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Name="RowDefinition0" Height="Auto"/>
<RowDefinition Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<TabPanel Name="headerPanel"
Background="Transparent"
Grid.Column="0"
IsItemsHost="true"
Margin="2,2,2,0"
Grid.Row="0"
KeyboardNavigation.TabIndex="1"
Panel.ZIndex="1"/>
<Border Name="contentPanel"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Column="0"
KeyboardNavigation.DirectionalNavigation="Contained"
Grid.Row="1"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<DockPanel Background="White">
<Grid Name="TabControlHeader" DockPanel.Dock="Top" Height="65">
<Label x:Name="SelectedItemTitle" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" Content="How to bind to AP ItemTitle?"/>
</Grid>
<Grid Name="Detail" Margin="8,0,8,8">
<Border BorderThickness="3,3,0,0" BorderBrush="DarkGray" CornerRadius="3"/>
<Border BorderThickness="2,2,1,1" BorderBrush="LightGray" CornerRadius="3"/>
<Border BorderThickness="1,1,1,1" BorderBrush="White" CornerRadius="3" Margin="3,3,-1,-1" Padding="5">
<Viewbox>
<ContentPresenter Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Viewbox>
</Border>
</Grid>
</DockPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<TabControl x:Name="TabCtl">
<TabItem Header="Tab1" local:MainWindow.ItemTitle="Tab1 Title" />
<TabItem Header="Tab2" local:MainWindow.ItemTitle="Tab2 Title" />
<TabItem Header="Tab3" local:MainWindow.ItemTitle="Tab3 Title" />
</TabControl>
</Window>
I'd like the respective title entries to be displayed in the TabControl's SelectedItemTitle label.
Any hints appreciated, even a definitive "That's not possible" would be good to know, so I can stop trying 😁
The property (sub-)path for an attached property needs to be enclosed in parentheses:
Content="{Binding Path=SelectedItem.(local:MainWindow.ItemTitle),
RelativeSource={RelativeSource AncestorType=TabControl}}"
See PropertyPath for Objects in Data Binding for details.
An attached property is not even required. You could as well use the TabItem's Tag property like
<TabItem Header="Tab1" Tag="Tab1 Title"/>
with
Content="{Binding Path=SelectedItem.Tag,
RelativeSource={RelativeSource AncestorType=TabControl}}"

How could providing Parent Window as Pattern - WPF

Consider I have 4 Window on my project and I try to provide specific Closing button and one Title
How could I make a object of window and all of window use it as Pattern.
Here is Example of what we have for pattern window:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None" AllowsTransparency="True" >
<Grid>
<Button Content="Close" Height="40" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBlock VerticalAlignment="Top" HorizontalAlignment="Center" X:Name="WindowTitle/>
</Grid>
</Window>
How could I use for all of my Window as pattern. Thanks
Actually, there is no need to write a parent window. You can use Style and Template instead. It's more convenient and is recommended by Microsoft WPF Team.
Write the code below into your App.xaml and you'll get the picture above:
<Application x:Class="Walterlv.Demo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
.........>
<Application.Resources>
<Style x:Key="Style.Window.Default" TargetType="Window">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Window">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Close" Height="40" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Center"
Text="{TemplateBinding Title}"/>
<Border Grid.Row="1" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<!-- This is the container to host your Window.Content -->
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
And you can use only one property Style to share such a "pattern":
<Window x:Class="Walterlv.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Style="{StaticResource Style.Window.Default}">
</Window>
You can define different kinds of styles in the App.xaml file and select anyone in your XxxWindow.xaml you need.

Why do I need to type ContentControl.Content and not just Content

I am watching an online video tutorial about DataTemplates
The demo code is the follows
<StackPanel.Resources>
<ControlTemplate x:Key="MyButton">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<ContentControl Content="{TemplateBinding ContentControl.Content}" /><!--why this-->
</Grid>
</ControlTemplate>
</StackPanel.Resources>
<Button Content="Click me" Background="Green" Width="100" Height="50" Template="{StaticResource MyButton}" />
</StackPanel>
The bit where I'm lost is
<ContentControl Content="{TemplateBinding ContentControl.Content}" />
Why is it ControlControl.Content and not just Content. If we review the line of code before this it shows Ellipse Fill="{TemplateBinding Background}" and not <Ellipse Fill="{TemplateBinding Ellipse.Background}" />
Why do we state ContentControl? Is it because the property Content of the Button is actually an object of type ContentControl where as Background is just a string property of Button?
If you specify the type of the ControlTemplate (also the same for Style), then the compiler will know what object to look at to find the Content property:
<ControlTemplate x:Key="MyButton" TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<ContentControl Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
If you don't specify the type, the compiler will require you to enter the type in the TemplateBinding path as you have.
However, sometimes you can also specify which base class a property is from as in this case, because the Content property is actually inherited in the Button Class from the ContentControl class... you can see this by clicking on the Content property in the Button Class page on MSDN, which will take you to the ContentControl.Content Property page.
You should find that in this case, you could also use Button.Content as the property is inherited in the Button Class. Therefore, to answer your question, you don't actually need to use ContentControl.Content... you have a choice.
UPDATE >>>
You can do that with Styles, but with ControlTemplates, you'd have to make sure that the objects that you apply it to also have the same base class and all properties used in the ControlTemplate. Theoretically, you could then do this:
<ControlTemplate x:Key="MyButton" TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<ContentControl Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
...
<RadioButton Template="{StaticResource MyButton}" Content="Hey" />
However, I imagine that you actually need to use a ContentPresenter instead of the ContentControl:
<ControlTemplate x:Key="MyButton" TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<ContentPresenter />
</Grid>
</ControlTemplate>
You may also like to read the What's the difference between ContentControl and ContentPresenter? page here on Stack Overflow.

How do I get rid of this WPF/XAML ListBox Padding?

I'm having some trouble learning WPF, I've set up a custom UserControl like so:
<UserControl
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:LobbyApp" x:Class="LobbyApp.ChatPanel"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="600">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ChatMessage}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
<ColumnDefinition.Width>Auto</ColumnDefinition.Width>
<ColumnDefinition.SharedSizeGroup>Date</ColumnDefinition.SharedSizeGroup>
</ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition.Width>Auto</ColumnDefinition.Width>
<ColumnDefinition.SharedSizeGroup>Who</ColumnDefinition.SharedSizeGroup>
</ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition.Width>*</ColumnDefinition.Width>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGridCell BorderThickness="0" Content="{Binding Timestamp}" Grid.Column="0" Background="Black" Foreground="LightCyan"/>
<DataGridCell BorderThickness="0" Content="{Binding Who}" Grid.Column="1" Background="Black" Foreground="LightBlue"/>
<DataGridCell BorderThickness="0" Grid.Column="2" Background="Black" Foreground="White">
<TextBox Text="{Binding What, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap"/>
</DataGridCell>
</Grid>
</DataTemplate>
</UserControl.Resources>
<ListBox ItemsSource="{Binding History}" SnapsToDevicePixels="True" Background="Magenta" Padding="0"/>
Most of that I think doesn't matter, just including it for completeness. The interesting bit is I see the magenta background outside the scrollbars, like the listbox content and it's scrollbars are actually padded inside the listbox. It looks like this:
I see the outer magenta even if the listbox is a reasonable size, it's just easier to see when you shrink it small like that.
I've tried every margin/padding on every element I can to get rid of the magenta, but I can't seem to. I don't know what is causing that, or how to 'fix' it. I'll probably simplify my example down to the most basic parts but thought I'd post first since maybe it's just a dumb obvious answer. My apologies if so.
This is because the ListBox has a hard coded value of '1' for the Border Padding.
<ControlTemplate TargetType="{x:Type ListBox}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true"
Padding="1">
<ScrollViewer Padding="{TemplateBinding Padding}"
Focusable="false">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
So you can either modify the template yourself, or just hack it like below :
void OnListBoxLoaded(object sender, RoutedEventArgs e)
{
Border border = VisualTreeHelper.GetChild((ListBox)sender, 0) as Border;
if(border != null)
{
border.Padding = new Thickness(0);
}
}
The above makes an assumption that you are using the default windows aero theme. If you change theme, or indeed if the aero theme gets changed, it may break.

WPF DataTemplate property set at Content

New to WPF and have Tabs and in each tab the content is presented in a curved corner panel/window/whateveryouwannacallit. I wasn't sure how to do this ( Style, ControlTemplate ) but decided to go the DataTemplate way.
So now I have this DataTemplate:
<DataTemplate x:Key="TabContentPresenter" >
<Border Margin="10"
BorderBrush="{StaticResource DarkColorBrush}"
CornerRadius="8"
BorderThickness="2"
Grid.Row="0"
Padding="5"
Background="{TemplateBinding Background}">
<ContentPresenter Content="{Binding}" />
</Border>
</DataTemplate>
As you can see with the the background property I wan't to set the background color at the content but Don't know how. Here I use it.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl ContentTemplate="{StaticResource TabContentPresenter}" Background="White">
<!-- Something Here -->
</ContentControl>
<ContentControl ContentTemplate="{StaticResource TabContentPresenter}" Grid.Row="1" Background="Blue">
<!-- Something Here -->
</ContentControl>
</Grid>
Is using DataTemplate wrong here or is there any other way?
I could probably set the background straight on the content and change from padding in mthe template to margin in the content but in some similiar situations that wouldn't work and it's nicer to only have to set it once.
EDIT:
As per advice I changed to ControlTemplate and also put it inside a style. This solves the Background problem but creates a bigger one. Now the content won't appear. I read on a blog here that putting a targetType solves this but it didn't solve my problem. The code looks like this now and also changed the ContentControl to use the style instead of Template.
<Style x:Key="TabContentPresenter" TargetType="ContentControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Border Margin="10"
BorderBrush="{StaticResource DarkColorBrush}"
CornerRadius="8"
BorderThickness="2"
Grid.Row="0"
Background="{TemplateBinding Background}">
<ContentPresenter Content="{Binding}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Use ControlTemplate instead DataTemplate
<ControlTemplate x:Key="TabContentPresenter">
<Border Margin="10"
CornerRadius="8"
BorderThickness="2"
Grid.Row="0"
Padding="5"
Background="{TemplateBinding Background}">
<ContentPresenter Content="{Binding}"/>
</Border>
</ControlTemplate>
Use Template instead of ContentTemplate
<ContentControl Background="Green" Template="{StaticResource TabContentPresenter}"/>
May be because TemplateBinding does not work with DataTemplate. Check this question for details.
Even if it works, all you need is a ControlTemplate and not a datatemplate.

Resources