WPF popup: how to make a reusable template for popups? - wpf

Since Popup doesn't derive from Control and doesn't have a template, how can I define a template so that all popups look the same? I need to design one that has a certain look and don't want to have to copy markup each time one is used.
This seems pretty easy but I can't figure out how to do it. The Child property defines a logical tree but I don't see how you can pull that out into a template and reuse it.

I was looking to do the same thing and here is what I came up with:
I inherited from ContentPresenter, styled that control as I wanted and than placed the derived ContentPresenter inside my Popup, I only used 2 text blocks for the simplicity but it is easy to understand how any content could be added.
My custom control:
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
[TemplatePart(Name = PART_PopupHeader, Type = typeof(TextBlock))]
[TemplatePart(Name = PART_PopupContent, Type = typeof(TextBlock))]
public class CustomPopupControl : ContentControl
{
private const string PART_PopupHeader = "PART_PopupHeader";
private const string PART_PopupContent = "PART_PopupContent";
private TextBlock _headerBlock = null;
private TextBlock _contentBlock = null;
static CustomPopupControl()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(CustomPopupControl),
new FrameworkPropertyMetadata(typeof(CustomPopupControl)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_headerBlock = GetTemplateChild(PART_PopupHeader) as TextBlock;
_contentBlock = GetTemplateChild(PART_PopupContent) as TextBlock;
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(CustomPopupControl), new UIPropertyMetadata(string.Empty));
public string HeaderText
{
get
{
return (string)GetValue(HeaderTextProperty);
}
set
{
SetValue(HeaderTextProperty, value);
}
}
public static readonly DependencyProperty ContentTextProperty =
DependencyProperty.Register("ContentText", typeof(string), typeof(CustomPopupControl), new UIPropertyMetadata(string.Empty));
public string ContentText
{
get
{
return (string)GetValue(ContentTextProperty);
}
set
{
SetValue(ContentTextProperty, value);
}
}
}
}
Style for the control:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Style TargetType="{x:Type local:CustomPopupControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomPopupControl}">
<Border CornerRadius="3" BorderThickness="1" BorderBrush="White">
<Border.Background>
<SolidColorBrush Color="#4b4b4b" Opacity="0.75"/>
</Border.Background>
<Border.Effect>
<DropShadowEffect ShadowDepth="0"
Color="White"
Opacity="1"
BlurRadius="5"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding HeaderText}"
Grid.Row="0"
Foreground="#5095d6"
FontWeight="Bold"
VerticalAlignment="Bottom"
Margin="{TemplateBinding Margin}"
HorizontalAlignment="Left"/>
<Rectangle Grid.Row="1" Stroke="AntiqueWhite" Margin="1 0"></Rectangle>
<TextBlock Grid.Row="2"
Grid.ColumnSpan="2"
x:Name="PART_TooltipContents"
Margin="5, 2"
Text="{TemplateBinding ContentText}"
TextWrapping="Wrap"
MaxWidth="200"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The use of the control:
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="Button1" Content="Button with popup" HorizontalAlignment="Center">
</Button>
<Button x:Name="Button2" Content="Another button with popup" HorizontalAlignment="Center">
</Button>
<Popup IsOpen="True"
FlowDirection="LeftToRight"
Margin="10"
PlacementTarget="{Binding ElementName=Button1}"
Placement="top"
StaysOpen="True">
<local2:CustomPopupControl HeaderText="Some Header Text" ContentText="Content Text that could be any text needed from a binding or other source" Margin="2">
</local2:CustomPopupControl>
</Popup>
<Popup IsOpen="True"
FlowDirection="LeftToRight"
Margin="10"
PlacementTarget="{Binding ElementName=Button2}"
Placement="Bottom"
StaysOpen="True">
<local2:CustomPopupControl HeaderText="Different header text" ContentText="Some other text" Margin="2">
</local2:CustomPopupControl>
</Popup>
</StackPanel>
I tried illustrating how some properties can be constant across all controls, others can be customized per control and others could be bound to TemplatePart, here is the final result:

Depends how you want your pop-ups to behave. If they're just for displaying information in a uniform manner, than you might want to have a class that derives from Window that has the standard formats and styling wrapped around a ContentPresenter then bind the content of the presenter to a property which can represent the custom information for each pop-up.
Then its just a matter of programatically inserting whatever custom content you want before displaying the pop-up window.
Hope it helps.

Related

UserControl child fill Window

I have a UserControl that is a portion of a wpf window.
<Window>
<Grid>
<!--some other display elements would be here-->
<local:MyUserControl x:Name="Foo" Padding="0,42,0,50"/>
</Grid>
</Window>
Inside MyUserControl I have an element that is a gallery that is normally hidden, but when visible, it should fill the entire screen.
<UserControl>
<Grid>
<!--main display elements would be here-->
<Grid Name="Gallery" Visibility="Hidden">
<Rectangle Fill="Black" Opacity="0.75"/>
<TextBlock Name="GalleryLabel" Foreground="White" TextAlignment="Center">Current Image Title</TextBlock>
<Button Name="CloseGallery" Style="{DynamicResource WhiteTextButton}" Margin="0,0,0,0" Height="25" VerticalAlignment="Top" HorizontalAlignment="Right" Width="25" Click="GalleryClose_OnClick">X</Button>
<Image Name="GalleryImage" Margin="25"/>
</Grid>
</Grid>
</UserControl>
How can I set the Gallery to fill the entire Window rather than just the UserControl?
I was able to get it to work by adding Margin="0,-42,0,-50" to Gallery, but I don't like that solution. I would rather do something that doesn't involve hard-coding values in the UserControl so that I would be able to have more flexiblility in using it.
Normally it looks like:
where the green Foo area is MyUserControl, and the rest of the things in the window are other elements.
At certain points, I have a gallery display an image, which should fill the entire screen like:
which should fill the entire screen and have a black opaque overlay, along with an image displayed on top of the overlay.
Remove the Padding...
You can use:
<local:MyUserControl x:Name="Foo" VerticalAlignment="Stretch" HorizontalAligment="Stretch"/>
EDIT
I may admit that I still am not sure what you want. But I made something which does the same as you showed in the pictures. But be aware that this as an opinion based question, and the answer is my opinion, there are a lot of ways to achieve this. And this is only one of them:
MainWindow
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myProject="clr-namespace:MyProject"
Title="MainWindow" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Background="Gainsboro" Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Content="Show gallery" Width="100" Margin="10"/>
<Button Content="Do something else" Width="150" Margin="10"/>
</StackPanel>
</Border>
<myProject:SomeOtherStuff Grid.Row="1" />
<Border Grid.Row="2" Background="Gainsboro">
<StackPanel Orientation="Horizontal">
<Label Content="Buttom area, can be used for something else"/>
</StackPanel>
</Border>
<myProject:GalleryUserControl x:Name="GalleryUserControl" Grid.Row="0" Grid.RowSpan="3" Visibility="Hidden"/>
</Grid>
SomeOtherStuff UserControl
<UserControl x:Class="MyProject.SomeOtherStuff"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="Green">
<Label VerticalAlignment="Center" HorizontalAlignment="Center" Content="Foo" FontSize="30" FontFamily="Verdana"/>
</Grid>
Gallery UserControl
<UserControl x:Class="XamDataGrid.GalleryUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="500" Width="800">
<Grid Background="#99000000">
<TextBlock Text="Currect image title" Foreground="White" TextAlignment="Center" VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Button Name="CloseGallery" Margin="0,0,0,0" Content="X" Height="25" VerticalAlignment="Top" HorizontalAlignment="Right" Width="25" Click="GalleryClose_OnClick"/>
<Image Margin="30"/>
</Grid>
An Adorner will be best for you. As it floats above everything else and remains outside the VisualTree too.
UserControl2
<UserControl2 ...>
<Grid>
<Grid Background="#FF709FA6" Opacity="0.3" Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}">
</Grid>
<Grid Width="600" Height="700">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF37DAEA" Offset="0"/>
<GradientStop Color="#FFE84242" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<!-- Gallery controls -->
<Button Content="Welcome to Gallery" HorizontalAlignment="Left" IsHitTestVisible="True" Margin="112,126,0,0" VerticalAlignment="Top" Height="68" FontSize="48" Click="Button_Click_1"/>
<TextBlock HorizontalAlignment="Left" Margin="83,42,0,0" TextWrapping="Wrap" Text="UserControl2" VerticalAlignment="Top" Foreground="#FF1B0D0D"/>
</Grid>
</Grid>
</UserControl2>
UserControl1
Code :
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// get root Window
DependencyObject current = LogicalTreeHelper.GetParent(this);
while (!(current is Window))
current = LogicalTreeHelper.GetParent(current);
Window root = current as Window;
IEnumerable children = LogicalTreeHelper.GetChildren(root);
Panel p = null;
foreach (var child in children)
{
p = child as Panel; // get first container panel in Window
break;
}
// Create UserControl2 to add to the adorner
UserControl2 ctrlGallery = new UserControl2();
AdornerLayer layer = AdornerLayer.GetAdornerLayer(p);
GalleryAdorner adorner = new GalleryAdorner(p, ctrlGallery);
layer.Add(adorner);
}
}
public class GalleryAdorner : Adorner
{
private Control _child;
VisualCollection collection;
public GalleryAdorner(UIElement elem, Control child)
: base(elem)
{
collection = new VisualCollection(this);
_child = child;
collection.Add(_child);
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index != 0) throw new ArgumentOutOfRangeException();
return collection[0];
}
protected override Size MeasureOverride(Size constraint)
{
_child.Measure(constraint);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
_child.Arrange(new Rect(new Point(0, 0), finalSize));
return new Size(_child.ActualWidth, _child.ActualHeight);
}
}

Trying to create a custom panel but getting a: The object already has a child and cannot add 'Button' compiler exception

I am pretty new to wpf.
I am trying to make a custom panel with some fancy design borders.
I got everything working but only if I have only 1 control in the custom panel.
If I try to add more than one then I get the following exception:
The object 'BordersPanel' already has a child and cannot add 'Button'. 'BordersPanel' can accept only one child.
I spent a lot of time trying to find a solution on the net, but so far nothing.
So I am turning to the community for help.
The panel style:
<Style
TargetType="{x:Type local:BordersPanel}">
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type local:BordersPanel}">
<Grid Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="10.8" />
<RowDefinition Height="*" />
<RowDefinition Height="10.8" />
</Grid.RowDefinitions>
<local:Border
x:Name="topBorder"
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="top"
BlocksSize="{TemplateBinding BlocksSize}"
Width="Auto"
Height="10.8"/>
<ScrollViewer
Grid.Row="1"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ContentPresenter
Cursor="{TemplateBinding Cursor}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</ScrollViewer>
<local:Border
x:Name="bottomBorder"
Grid.Row="2"
Margin="0"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
BlocksSize="{TemplateBinding BottomBlocksSize}"
Width="Auto"
Height="10.8"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The C# code behind:
class BordersPanel : ContentControl
{
public static readonly DependencyProperty BlocksSizeProperty =
DependencyProperty.Register("BlocksSize", typeof(string), typeof(BordersPanel));
public static readonly DependencyProperty BottomBlocksSizeProperty =
DependencyProperty.Register("BottomBlocksSize", typeof(string), typeof(BordersPanel));
private Border topBorder;
private Border bottomBorder;
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == BordersPanel.BlocksSizeProperty)
{
BottomBlocksSize = BlocksSize + ";T";
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
topBorder = GetTemplateChild("topBorder") as Border;
bottomBorder = GetTemplateChild("bottomBorder") as Border;
}
static BordersPanel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BordersPanel),
new FrameworkPropertyMetadata(typeof(BordersPanel)));
}
public BordersPanel()
{
SizeChanged += new SizeChangedEventHandler(Border_SizeChanged);
}
public string BlocksSize
{
get
{
return (string)GetValue(BlocksSizeProperty);
}
set
{
SetValue(BlocksSizeProperty, value);
}
}
protected string BottomBlocksSize
{
get
{
return (string)GetValue(BottomBlocksSizeProperty);
}
set
{
SetValue(BottomBlocksSizeProperty, value);
}
}
private void Border_SizeChanged(object sender, SizeChangedEventArgs e)
{
double available = Width;
if (Double.IsNaN(available) == true)
{
available = ActualWidth;
}
if (topBorder != null)
{
topBorder.Width = available;
}
if (bottomBorder != null)
{
bottomBorder.Width = available;
}
}
}
And this is how I use it:
<local:BordersPanel
BlocksSize="LL50;ML50;RU"
Height="208" Canvas.Left="213" Canvas.Top="56.82" Width="428.5">
<local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
<Button Content="Button" HorizontalAlignment="Stretch" Height="300"/>
</local:BordersPanel>
I am pretty sure that I am missing something pretty obvious, but that's the thing with obvious things, the more you look at them, the more they elude you...
By they way, if you think there are better ways to do what I did (with the binding, panel,...), comments are welcomed. :)
To be very clear:
Before I was using:
<local:Border
Margin="0"
VerticalAlignment="top"
BlocksSize="LL50;ML200;RU"
Width="Auto" Height="10.8" HorizontalAlignment="Stretch" d:LayoutOverrides="Width"/>
<local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
<Grid Margin="8,14.8,8,24">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="pnl_Content5" Margin="0,0,0,0">
<Button Content="Button" HorizontalAlignment="Stretch" Canvas.Left="77" Canvas.Top="44"/>
</Canvas>
</ScrollViewer>
</Grid>
<local:Border
Margin="0"
VerticalAlignment="Bottom"
BlocksSize="LL50;ML200;RU;T"
Width="Auto" Height="10.8" HorizontalAlignment="Stretch"/>
I am using this code over and over each time I use a panel.
Obviously what I wrote in the previous code block is shorter and easier to maintain. (At least in my opinion)
That is why I made this custom panel with the border to avoid duplicating code.
Well I found a solution that is working.
It's not exactly what I wanted, but it's close enough that it will do.
I simply put a grid declaration inside the BordersPanel instance.
I would have preferred to not have to do this, but I am guessing that that way it actually allows me to use any type of layout inside the panel.
<local:BordersPanel
BlocksSize="LL50;ML50;RU"
Margin="0">
<Grid>
<local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
<Button Content="Button" HorizontalAlignment="Stretch" Height="300"/>
</Grid>
</local:BordersPanel>

Binding from an XAML resource dictionary to a class property

I am having a very difficult time setting up a binding which I think should be easy. Help is greatly appreciated.
I have a resource dictionary named FormResource.xaml. In this dictionary contains a Style for the ScrollView that I redine the template for. The purpose is I want a wider vertical scrollbar on it.
<Style x:Key="LargeScrolling" TargetType="ScrollViewer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter x:Name="ScrollContentPresenter"
Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<ScrollBar x:Name="PART_VerticalScrollBar"
Style="{StaticResource LargeVerticalScrollBar}"
Width="{Binding ElementName=MDTForm, Path=ScrollBarWidth}"
IsTabStop="False"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Grid.Column="1" Grid.Row="0" Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0"
Value="{TemplateBinding VerticalOffset}"
Margin="0,-1,-1,-1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a UserControl named FormControl.
public class FormControl : UserControl
I used to have this as a partial class with a XAML componenet, in which what I am trying to do worked, but I had to remove the XAML since I derive from this class in another assembly and WPF does not allow you to derive from a partial class in another assembly.
In FormControl I define a ScrollBarWidth property.
public static readonly DependencyProperty ScrollBarWidthProperty = DependencyProperty.Register("ScrollBarWidth", typeof(double), typeof(FormControl));
public double ScrollBarWidth
{
get { return (double)base.GetValue(ScrollBarWidthProperty); }
set { base.SetValue(ScrollBarWidthProperty, value); }
}
When I had this as a partial class in the main declaration I gave the FormControl class a Name of MDTForm, which is what I am using as the ElementName in my binding. I tried registering this name in FormClass.cs but no matter what I do the scrollbar is not picking up the property value.
Here is where I create my ScrollViewer in the FormControl class.
_canvasScrollViewer = new ScrollViewer();
_canvasScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
_canvasScrollViewer.VerticalAlignment = VerticalAlignment.Top;
_canvasScrollViewer.MaxHeight = Constants.ScrollViewMaxHeight;
_canvasScrollViewer.Style = (Style)FindResource("LargeScrolling");
The only way that I got this to work was to bind to a static property. I used this for the binding.
Width="{Binding Source={x:Static form:FormControl.ScrollBarWidthP}}"
Then defined the property as such.
public static double ScrollBarWidth { get; set; }
However, I don't want this as I can have multiple FormControl objects loaded at the same time and they may not all have the same scroll bar width property.
Use a RelativeSource Binding instead of ElementName:
{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type controls:FormControl}}, Path=ScrollBarWidth}
This will walk up the visual tree at runtime to find the parent control containing the ScrollViewer, which solves both your scoping and multiple instance issues.

ContentControl Template through Property

I have a Usercontrol with a ControlTemplate DependencyProperty (named MyItemTemplate).
public ControlTemplate MyContentControl
{
get { return (ControlTemplate)GetValue(MyContentControlProperty); }
set { SetValue(MyContentControlProperty, value); }
}
public static readonly DependencyProperty MyContentControlProperty =
DependencyProperty.Register("MyContentControl", typeof(ControlTemplate), typeof(MyScroll),
new PropertyMetadata(new ControlTemplate()));
In the xaml of my UserControl I want to use the "MyItemTemplate" as a template for a ContentControl like that :
<ContentControl x:Name="MyContentControl" Template="{Binding MyItemTemplate}" />
I know that the Template="{Binding MyItemTemplate}" is wrong, but I wonder how to do it...
Thanks
You can use a RelativeSource binding to reference a custom DependencyProperty on your UserControl
<ContentControl Template="{Binding
RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}},
Path=MyItemTemplate}" />
Edit
If you're working in Silverlight 4.0 or lower, which doesn't support RelativeSource bindings, then give your UserControl tag a Name and use an ElementName binding
<UserControl x:Name="MyUserControl" ...>
<ContentControl Template="{Binding ElementName=MyUserControl, Path=MyItemTemplate}" />
</UserControl>
Have your template as a static resource (defined in your XAML somewhere).
<DataTemplate x:Key="DetailedTemplate">
<Border BorderBrush="Blue" Margin="3" Padding="3" BorderThickness="2" CornerRadius="5" Background="Beige">
<StackPanel Orientation="Horizontal">
<Image Margin="10" Width="250" Height="200" Stretch="Fill" Source="{Binding Path=ImageHref}">
<Image.BitmapEffect>
<DropShadowBitmapEffect />
</Image.BitmapEffect>
</Image>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock FontSize="25" Foreground="Goldenrod" Text="{Binding Path=ImageName}" />
<Label Content="{Binding Path=ImageRating,Converter={StaticResource RatingConverter}}" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="SimpleTemplate">
<Border BorderBrush="Blue" Margin="3" Padding="3" BorderThickness="2" CornerRadius="5" Background="Beige">
<StackPanel HorizontalAlignment="Center">
<Image Margin="10" Width="250" Height="200" Stretch="Fill" Source="{Binding Path=ImageHref}">
<Image.BitmapEffect>
<DropShadowBitmapEffect />
</Image.BitmapEffect>
</Image>
</StackPanel>
</Border>
</DataTemplate>
For example, in XAML:
<ListBox x:Name="lbResults" Grid.Row="1" Grid.Column="0" Height="240"
HorizontalContentAlignment="Stretch" ItemsSource="{StaticResource FavoriteImages}"
ItemTemplate="{StaticResource SimpleTemplate}" />
Then in the code behind something like:
//pull the detailed template from resources, identified by the DetailedTemplate key
DataTemplate detail = this.FindResource("DetailedTemplate") as DataTemplate;
lbResults.ItemTemplate = detail;
and
//pull the summary template from resources, identified by the SimpleTemplate key
DataTemplate summary = this.FindResource("SimpleTemplate") as DataTemplate;
lbResults.ItemTemplate = summary;
Although the best answer is Rachel's, here are some alternatives.
If this logic is not critical, you'd better put the template into resources and get it using StaticResource:
<UserControl>
<UserControl.Resources>
<ControlTemplate x:Key="template">
...
</ControlTemplate>
</UserControl.Resources>
<ContentControl Template="{StaticResource template}"/>
</UserControl>
If you still need to set it from the UserControl's property, you may either define a change callback.
XAML:
<UserControl>
<ContentControl x:Name="contentControl"/>
</UserControl>
Code-behind:
public ControlTemplate MyContentControl
{
get { return (ControlTemplate)GetValue(MyContentControlProperty); }
set { SetValue(MyContentControlProperty, value); }
}
public static readonly DependencyProperty MyContentControlProperty =
DependencyProperty.Register("MyContentControl", typeof(ControlTemplate), typeof(MyScroll), new PropertyMetadata(null, OnMyContentControlChanged));
static void OnMyContentControlChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var userControl = (MyScroll)sender;
userControl.contentControl.Template = e.NewValue as ControlTemplate;
}
And the last option is using a Custom Control.
Code:
public class MyScroll : SomeParentControl
{
public MyScroll()
{
this.DefaultStyleKey = typeof(MyScroll);
}
public ControlTemplate MyContentControl
{
get { return (ControlTemplate)GetValue(MyContentControlProperty); }
set { SetValue(MyContentControlProperty, value); }
}
public static readonly DependencyProperty MyContentControlProperty =
DependencyProperty.Register("MyContentControl", typeof(ControlTemplate), typeof(MyScroll), new PropertyMetadata(null));
}
The template:
<!-- This is a template for what have been your UserControl -->
<ControlTemplate TargetType="{x:Type someNameSpaceAlias:MyScroll}">
<!-- And this is the 'MyContentControl' -->
<ContentControl Template="{TemplateBinding MyContentControl}"/>
</ControlTemplate>

Align two pieces of text separably in a WPF button

I have a reasonably simple button ControlTemplate to style a button.
I have been asked to align a portion of the text for the button to the right of the button. Therefore Some text will be aligned left and some right.
A pointer to an approach for this would be appreciated.
This is what I have so far:
<ControlTemplate x:Key="ListItemNameTemplate" TargetType="Button">
<Grid Height="40">
<Border Width="200"
x:Name="BgEnabled"
HorizontalAlignment="Center"
Margin="1,1,1,1"
Background="YellowGreen">
<StackPanel Orientation="Vertical">
<TextBlock Width="150"
x:Name="textBlock"
Text="{TemplateBinding Content}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="White"
FontSize="24"
FontWeight="Bold"/>
<TextBlock Width="50"
x:Name="textBlock1"
Text="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="White"
FontSize="24"
FontWeight="Bold"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
however:
a. The second textblock does not display.
b. I need to specify the content for the second textblock differently than "Content".
You can subclass Button to introduce a new dependency property for the right-aligned text, you will be stuck with the Content property though (you could create two new properties and throw an exception if Content is set though).
To get the layout right you should either use a Dockpanel (dock left and right, LastChildFill set to false) or a Grid (with three columns, middle takes rest, left & right is auto-sized).
Like this:
<Window.Resources>
<ControlTemplate TargetType="{x:Type Button}"
x:Key="MyButtonTemplate">
<Border>
<DockPanel LastChildFill="True">
<TextBlock Text="{TemplateBinding Content}"
DockPanel.Dock="Top"/>
<TextBlock Text="{TemplateBinding Content}"
TextAlignment="Right" />
</DockPanel>
</Border>
</ControlTemplate>
</Window.Resources>
<Button Template="{StaticResource MyButtonTemplate}">
Hello World
</Button>
Looks like:
If you want the second text different then just create a second property. I would create an attached dependency property so you can still use TextBox without having to subclass it and screw up all the UI elements in your project. Use the snippet "propa" to get started in Visual Studio. It's easy. See here: http://msdn.microsoft.com/en-us/library/ms749011.aspx
Like this:
public class MyButtonThing
{
public static string GetText2(DependencyObject obj)
{
return (string)obj.GetValue(Text2Property);
}
public static void SetText2(DependencyObject obj, string value)
{
obj.SetValue(Text2Property, value);
}
public static readonly DependencyProperty Text2Property =
DependencyProperty.RegisterAttached("Text2",
typeof(string), typeof(System.Windows.Controls.Button));
}
And this:
<Window.Resources>
<ControlTemplate TargetType="{x:Type Button}"
x:Key="MyButtonTemplate">
<Border>
<DockPanel LastChildFill="True">
<TextBlock Text="{TemplateBinding Content}"
DockPanel.Dock="Top"/>
<TextBlock TextAlignment="Right" Text="{Binding
RelativeSource={RelativeSource AncestorType=Button},
Path=(local:MyButtonThing.Text2)} " />
</DockPanel>
</Border>
</ControlTemplate>
</Window.Resources>
<Button Template="{StaticResource MyButtonTemplate}"
local:MyButtonThing.Text2="Where's Waldo">
Hello World
</Button>
The attached property route was certainly the way to go to solve this issue. A new subject for me and the examples did not work for me. The control template was not able to recognize the new property.
The change that worked was the way to modify how the attached property is defined.
Thanks to this article for the pointer:
http://www.deepcode.co.uk/2008/08/exposing-new-properties-for-control_15.html
public class MyButtonThing : DependencyObject
{
public static readonly DependencyProperty SubTextProperty =
DependencyProperty.RegisterAttached("SubText",
typeof(String), typeof(MyButtonThing),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange));
public static void SetSubText(UIElement element, object o)
{
element.SetValue(SubTextProperty, o);
}
public static string GetSubText(UIElement element)
{
return (string)element.GetValue(SubTextProperty);
}
}

Resources