I have a Border inside a Grid which is displayed in a ChildWindow. I need to make the Border larger so I applied a RenderTransform to it. However the ChildWindow hasn't expanded to fit the scaled Border. It looks like the size is being calculated before the render transform is applied. This means that the border is now cropped and I can only see a 1/4 of it.
I've tried wrapping the Border in a ScrollViewer and a ViewBox, neither of which have worked.
<Grid>
...
<Border x:Name="Border"
Grid.Row="3"
Grid.ColumnSpan="2"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
VerticalAlignment="Top"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}">
<Border.RenderTransform>
<ScaleTransform CenterX="0.5"
CenterY="0.5"
ScaleX="2"
ScaleY="2"/>
</Border.RenderTransform>
...
</Border>
...
</Grid>
How can I get the ChildWindow to use the ultimate size of the border when calculating its size?
I can't apply either the scale to the ChildWindow or use the rendered height and width of the Border directly as there are other elements in the Grid that I don't want to be scaled.
At the moment I can think of two possible solutions:
1. Calculate Scaled Properties for Your Border to Bind to
<Grid>
<Grid.Resources>
<ScaledSizeProperties x:Key="BorderSizes"
ScaleFactor="2"
BorderThickness="1"
CornerRadius="5"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}"/>
</Grid.Resources>
...
<Border x:Name="Border"
Grid.Row="3"
Grid.ColumnSpan="2"
Background="White"
BorderBrush="Black"
VerticalAlignment="Top"
BorderThickness="{Binding
Path=ScaledBorderThickness, Source={StaticResource BorderSizes}}"
CornerRadius="{Binding
Path=ScaledCornerRadius, Source={StaticResource BorderSizes}}"
Height="{Binding
Path=ScaledHeight, Source={StaticResource BorderSizes}}"
Width="{Binding
Path=ScaledWidth, Source={StaticResource BorderSizes}}">
...
</Border>
...
</Grid>
and code:
public class ScaledSizeProperties : DependencyObject
{
//add input DependencyProperties for:
//double ScaleFactor
//double BorderThickness
//double CornerRadius
//double Height
//double Width
//add output DependencyProperties for:
//double ScaledBorderThickness
//double ScaledCornerRadius
//double ScaledHeight
//double ScaledWidth
public double ScaleFactor
{
get { return (double) GetValue( ScaleFactorProperty ); }
set { SetValue( ScaleFactorProperty, value ); }
}
public static readonly DependencyProperty ScaleFactorProperty =
DependencyProperty.Register( "ScaleFactor", typeof( double ),
typeof( ScaledSizeProperties ),
new PropertyMetadata( 1, OnScaleFactorChanged ) );
private static void OnScaleFactorChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//recalculate all size properties
}
public double Height
{
get ...
set ...
}
... DependencyProperty HeightProperty ... OnHeightChanged ...
private static void OnHeightChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//recalculate ScaledHeight
}
}
2. Use a LayoutTransformer instead of RenderTransform
The LayoutTransformer that is part of the Toolkit is now (since SL5) official part of the base library package of Silverlight.
<Grid>
...
<LayoutTransformer
Grid.Row="3"
Grid.ColumnSpan="2"
VerticalAlignment="Top">
<LayoutTransformer.LayoutTransform>
<ScaleTransform CenterX="0.5"
CenterY="0.5"
ScaleX="2"
ScaleY="2"/>
</LayoutTransformer.LayoutTransform>
<Border x:Name="Border"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}">
...
</Border>
</LayoutTransformer>
...
</Grid>
Related
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>
I'm trying to make my popup widget to be on top in a map but setting Canvas.ZOrder doesn't help.
Here is XAML:
<m:Map x:Name="MainMap"
Margin="0,6,3,3"
ZoomLevel="{Binding MapZoomLevel, Mode=TwoWay}"
Center="{Binding MapCenter, Mode=TwoWay}"
CopyrightVisibility="Collapsed"
CredentialsProvider="{Binding BingCredentialsProvider}"
UseInertia="True"
Mode="Road" Grid.Column="2" Grid.Row="1">
<m:MapItemsControl ItemsSource="{Binding Source={StaticResource WorkLayerData}}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<Canvas
m:MapLayer.Position="{Binding Location}">
<Button
Width="{Binding PushpinWidth}" Height="{Binding PushpinWidth}"
Margin="{Binding PushpinMargin}"
Style="{StaticResource LooklessButtonStyle}"
Command="{Binding DataContext.SelectedPushpinChangedCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding}"
Cursor="Hand">
<Ellipse
Width="{Binding PushpinWidth}" Height="{Binding PushpinWidth}" Stroke="Black" Fill="{Binding IsGPSDataRecent, Converter={StaticResource BoolToGreenRedBrushConverter}}" StrokeThickness="1">
<ToolTipService.ToolTip>
<TextBlock Text="{Binding DeviceId}" />
</ToolTipService.ToolTip>
</Ellipse>
</Button>
<!-- Show black dot over actual GPS point -->
<Ellipse
Width="10" Height="10" Stroke="Black" Fill="Black" StrokeThickness="1"
Margin="-5,-5,0,0"
Visibility="{Binding IsSelected, Converter={StaticResource BoolToVisibilityConverter}}" />
<Border
Width="200"
BorderThickness="1" BorderBrush="DarkGray"
Visibility="{Binding IsSelected, Converter={StaticResource BoolToVisibilityConverter}}">
<Border.Effect>
<DropShadowEffect BlurRadius="5" Color="#FF000000" Opacity="0.5" ShadowDepth="2" />
</Border.Effect>
<ContentControl Template="{StaticResource TrackedAssetControlTemplate}" />
</Border>
</Canvas>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
</m:Map>
I tried to set ZIndex on a border but no luck.
Here is how it looks when IsSelected = true (see other dots with ZIndex higher on top)
In order to bring an item in a MapItemsControl to front it is necessary to set the ZIndex of the item container. You can do that in code behind by retrieving the item container from the MapItemsControl's ItemContainerGenerator.
In case you don't want that, you could apply an attached helper property to the top-level container (the Canvas) in the DataTemplate for your map items. As this Canvas is the direct child of the item container, the helper property would have to set the ZIndex of the visual parent of the Canvas. If that sounds weird, here is the code for the attached property, in a helper class called MapItem:
public class MapItem
{
public static readonly DependencyProperty ZIndexProperty =
DependencyProperty.RegisterAttached("ZIndex", typeof(int),
typeof(MapItem), new PropertyMetadata(ZIndexChanged));
public static int GetZIndex(DependencyObject obj)
{
return (int)obj.GetValue(ZIndexProperty);
}
public static void SetZIndex(DependencyObject obj, int value)
{
obj.SetValue(ZIndexProperty, value);
}
private static void ZIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// set ZIndex on parent of obj
Canvas.SetZIndex((UIElement)VisualTreeHelper.GetParent(obj), (int)e.NewValue);
}
}
In your DataTemplate you may now bind the helper property to one of your VM properties, perhaps by using an appropriate binding converter:
<DataTemplate x:Key="MapItemDataTemplate">
<!-- setting the helper property MapItem.ZIndex on Canvas
sets the Canvas.ZIndex property on the item container -->
<Canvas local:MapItem.ZIndex="{Binding ...}">
...
</Canvas>
</DataTemplate>
I created a silverlight template control. Thouse control consist 4 elements: 2 textbox and 2 textblock.
markup (in generic.xaml):
<Style TargetType="local:InputForm">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:InputForm">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Login" Grid.Column="0" Grid.Row="0"/>
<TextBlock Text="Password" Grid.Column="0" Grid.Row="1"/>
<TextBox x:Name="LoginTextBox" Grid.Column="1" Grid.Row="0" Text="Login..."/>
<TextBox x:Name="PasswordTextBox" Grid.Column="1" Grid.Row="1" Text="Password..."/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In code file I get the textbox from template and set Foreground.Opacity property equels 0.5.
code:
public class InputForm : Control
{
private TextBox _loginTextBox;
private TextBox _passwordTextBox;
public InputForm()
{
this.DefaultStyleKey = typeof(InputForm);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_loginTextBox = this.GetTemplateChild("LoginTextBox") as TextBox;
_passwordTextBox = this.GetTemplateChild("PasswordTextBox") as TextBox;
SetInActive();
}
private void SetInActive()
{
_loginTextBox.Foreground.Opacity = .5;
_passwordTextBox.Foreground.Opacity = .5;
}
}
When I added this control in my silverlight application all textboxs element began represent text with Foreground.Opacity = 0.5
Start application:
Select "Login" tab:
Back to "Some infromation" tab:
Sample located here: http://perpetuumsoft.com/Support/silverlight/SilverlightApplicationOpacity.zip
Is it silverlight bug or I do something wrong?
The problem is that the Foreground property is of type Brush which is a reference type (a class).
When you assign .Opacity = 0.5 you are changing the opacity value of the referenced Brush. All other elements that are referencing the same brush will be affected.
Ordinarily we would use a Storyboard in VisualStateManager in the control template to specify the visual appearance of a control in different "states".
However a quick fix for your code would be:
private void SetInActive()
{
Brush brush = new SolidColorBrush(Colors.Black) { Opacity = 0.5 };
_loginTextBox.Foreground = brush
_passwordTextBox.Foreground= brush
}
I want to create a simple breadcrumb bar with ListView. Following a simple wireframe screenshot what I would like to archive in the future:
Now, I already created already some code, mainly doing it with DataTemplates, which works actually quite well, but I have some visual problems I am not able to solve:
Main problem concerns the different width of the items. The center of an "arrow" should be stretched and the tail and head should be a fixed width...
The other problem is the visual style of the first and last items
Here's the actual code:
<ListView DockPanel.Dock="Left" ItemsSource="{Binding TagList}"
MinWidth="300" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden" Margin="8,0,0,0">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="-8,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FFC64242" Data="F1 M 112,144L 104,144L 112,160L 104,176L 112,176" HorizontalAlignment="Stretch" Height="Auto" VerticalAlignment="Stretch" Width="Auto"/>
<Grid HorizontalAlignment="Stretch" Height="Auto" VerticalAlignment="Stretch" Width="Auto" Grid.Column="1">
<Rectangle Stretch="Fill" Fill="#FFC64242" HorizontalAlignment="Stretch" Height="Auto" Margin="0.5" VerticalAlignment="Stretch" Width="Auto"/>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Data="F1 M 128,144L 160,144" HorizontalAlignment="Stretch" Height="1" Margin="0" VerticalAlignment="Top" Width="Auto"/>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Data="F1 M 128,176L 160,176" HorizontalAlignment="Stretch" Height="1" Margin="0" VerticalAlignment="Bottom" Width="Auto"/>
</Grid>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FFC64242" Data="F1 M 168,144L 176,160L 168,176" Height="Auto" VerticalAlignment="Center" Width="8" HorizontalAlignment="Right" Grid.Column="2" d:LayoutOverrides="GridBox"/>
<DockPanel LastChildFill="True" Grid.ColumnSpan="2" Grid.Column="1">
<Label DockPanel.Dock="Left" FontSize="12" Content="{Binding Content, FallbackValue=Tagname n/a}" HorizontalAlignment="Left" Grid.Column="0" VerticalAlignment="Center" d:LayoutOverrides="Height" Margin="8,0"/>
<Button DockPanel.Dock="Right" Content="X" Background="Transparent" FontSize="12" Command="{Binding RemoveTagBtn}" Grid.Column="0" Width="13.077" d:LayoutOverrides="Height" VerticalAlignment="Center" Margin="0,0,8,0"/>
<!--<Border Background="#FFf7f7f7" BorderBrush="#FFc9c9c9" BorderThickness="1" CornerRadius="4" HorizontalAlignment="Left" Margin="0,0,0,5.96" d:LayoutOverrides="Height"/> -->
</DockPanel>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now as I had to find an answer myself in short time, this is my current solution. Also if you do not need the "selectable" feature of ListBox, you can exchange it with ItemControl.
Here's the code. Please be aware that I've commented out the "IsSelected" Triggers for the ItemStyleContainer...
<ListBox Padding="0" DockPanel.Dock="Left" ItemsSource="{Binding TagList}"
MinWidth="300" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="8,0,0,0" Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="{DynamicResource LXBarButtonBackgroundNormal}"/>
<Setter Property="BorderBrush" Value="{DynamicResource LXBarButtonBorderNormal}"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<DockPanel LastChildFill="True" Margin="-8,0,0,0">
<Path DockPanel.Dock="Left" Stroke="{DynamicResource LXBarButtonBorderNormal}" Fill="{DynamicResource LXBarButtonBackgroundNormal}" Data="F1 M 112,144L 104,144L 112,160L 104,176L 112,176" Stretch="Fill" Height="32" Width="8" />
<Path DockPanel.Dock="Right" Stroke="{DynamicResource LXBarButtonBorderNormal}" Fill="{DynamicResource LXBarButtonBackgroundNormal}" Data="F1 M 168,144L 176,160L 168,176" Stretch="Fill" Height="32" Width="8" />
<Border Name="Border" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" Padding="{TemplateBinding Padding}" BorderThickness="0,1" VerticalAlignment="Center">
<ContentPresenter />
<!--
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background"
Value="Blue"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
-->
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel VerticalAlignment="Center" Height="30">
<local:LXImageButton BorderThickness="0" Style="{DynamicResource LXBarImageButton}" Padding="0" DockPanel.Dock="Right" Background="Transparent" Command="{Binding RemoveTagBtn}" Height="16" Width="16"
NormalImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_normal.png"
ActiveImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_active.png"
HoverImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_hover.png"
PressedImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_hover.png"
DisabledImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_passive.png"
/>
<Label DockPanel.Dock="Left" FontSize="12" Content="{Binding Content, FallbackValue=Tagname n/a}" VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I made a custom shape that renders the arrow that you want.
I used some code from the Rectangle class.
As for the part of styling the first and last elements. You will need some sort of AttachedBehavior that adjusts some property based on the item index.
using System;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Media;
namespace CustomShapes
{
public class SquaredArrow : Shape
{
protected Rect _rect = Rect.Empty;
#region TipOffset
/// <summary>
/// TipOffset Dependency Property
/// </summary>
public static readonly DependencyProperty TipOffsetProperty =
DependencyProperty.Register("TipOffset", typeof(double), typeof(SquaredArrow),
new FrameworkPropertyMetadata((double)10, FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets the TipOffset property. This dependency property
/// indicates ....
/// </summary>
[System.ComponentModel.Category("SquaredArrow")]
public double TipOffset
{
get { return (double)GetValue(TipOffsetProperty); }
set { SetValue(TipOffsetProperty, value); }
}
#endregion
public SquaredArrow()
{
Rectangle r = new Rectangle();
r.Measure(new Size(100, 100));
r.Arrange(new Rect(0, 0, 100, 100));
}
static SquaredArrow()
{
StretchProperty.OverrideMetadata(typeof(SquaredArrow), new FrameworkPropertyMetadata(Stretch.Fill));
}
protected override Geometry DefiningGeometry
{
get { return CreateShape(); }
}
/// <summary>
/// Return the transformation applied to the geometry before rendering
/// </summary>
public override Transform GeometryTransform
{
get
{
return Transform.Identity;
}
}
/// <summary>
/// This is where the arrow shape is created.
/// </summary>
/// <returns></returns>
private Geometry CreateShape()
{
double width = _rect.Width;
double height = _rect.Height;
double borderOffset = GetStrokeThickness() / 2d;
PathGeometry g = new PathGeometry();
PathFigure figure = new PathFigure();
figure.IsClosed = true;
figure.StartPoint = new Point(borderOffset, borderOffset);
figure.Segments.Add(new LineSegment(new Point(width - TipOffset + borderOffset, borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(width + borderOffset, height / 2d + borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(width + borderOffset - TipOffset, height + borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(borderOffset, height + borderOffset), true));
g.Figures.Add(figure);
return g;
}
/// <summary>
/// Updates DesiredSize of the Rectangle. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <param name="constraint">Constraint size is an "upper limit" that Rectangle should not exceed.</param>
/// <returns>Rectangle's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
if (Stretch == Stretch.UniformToFill)
{
double width = constraint.Width;
double height = constraint.Height;
if (Double.IsInfinity(width) && Double.IsInfinity(height))
{
return GetNaturalSize();
}
else if (Double.IsInfinity(width) || Double.IsInfinity(height))
{
width = Math.Min(width, height);
}
else
{
width = Math.Max(width, height);
}
return new Size(width, width);
}
return GetNaturalSize();
}
/// <summary>
/// Returns the final size of the shape and cachnes the bounds.
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
// Since we do NOT want the RadiusX and RadiusY to change with the rendering transformation, we
// construct the rectangle to fit finalSize with the appropriate Stretch mode. The rendering
// transformation will thus be the identity.
double penThickness = GetStrokeThickness();
double margin = penThickness / 2;
_rect = new Rect(
margin, // X
margin, // Y
Math.Max(0, finalSize.Width - penThickness), // Width
Math.Max(0, finalSize.Height - penThickness)); // Height
switch (Stretch)
{
case Stretch.None:
// A 0 Rect.Width and Rect.Height rectangle
_rect.Width = _rect.Height = 0;
break;
case Stretch.Fill:
// The most common case: a rectangle that fills the box.
// _rect has already been initialized for that.
break;
case Stretch.Uniform:
// The maximal square that fits in the final box
if (_rect.Width > _rect.Height)
{
_rect.Width = _rect.Height;
}
else // _rect.Width <= _rect.Height
{
_rect.Height = _rect.Width;
}
break;
case Stretch.UniformToFill:
// The minimal square that fills the final box
if (_rect.Width < _rect.Height)
{
_rect.Width = _rect.Height;
}
else // _rect.Width >= _rect.Height
{
_rect.Height = _rect.Width;
}
break;
}
return finalSize;
}
/// <summary>
/// Get the natural size of the geometry that defines this shape
/// </summary>
protected Size GetNaturalSize()
{
double strokeThickness = GetStrokeThickness();
return new Size(strokeThickness, strokeThickness);
}
protected double GetStrokeThickness()
{
return this.StrokeThickness;
}
/// <summary>
/// Render callback.
/// </summary>
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Stroke, GetStrokeThickness());
drawingContext.DrawGeometry(Fill, pen, DefiningGeometry);
}
}
}
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.