XAML Path sizing and positioning - silverlight

I am trying to design a template for a TabItem using paths and rectangles. The sides of the tab layout are paths to accommodate curves in the design. I am having a problem with getting the sides to line up/resize correctly. Here's my XAML:
<StackPanel Orientation="Horizontal">
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 97.1985,101.669L 104.824,95.0005C 105.574,94.3338 106.921,93.8627 107.824,93.9171L 107.824,101.667L 97.1985,101.669 Z "/>
<Grid>
<Rectangle Grid.Column="1" Fill="#FF000000"/>
<ContentControl Grid.Column="1" x:Name="HeaderTopSelected" FontSize="{TemplateBinding FontSize}" Foreground="{TemplateBinding Foreground}" IsTabStop="False" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
</Grid>
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 118.714,101.678L 111.088,95.0097C 110.338,94.343 108.991,93.8719 108.088,93.9264L 108.088,101.676L 118.714,101.678 Z "/>
</StackPanel>
(I apologize for the lack of line breaks. I'm new to StackOverflow and don't know how to insert them, nor do I have the time to figure it out :D)
The code snippet posted almost works: The side paths are the correct size, but they do not display in a line, they overlap behind the rectangle/next element. If I set a fixed width they work as well, but I can't set the width as fixed, they need to be fluid in case the content of the tab exceeds the basic height.
This is an idea of what I would like to get
The sides (paths) grow uniformally based on the height of the ContentControl

Take 3 (now we know what the requirement is):
Basically to get the desired effect you need your 2 side parts to resize their width if their height changes, in order to keep a fixed aspect ratio. This will force the parent container to expand as the actual content gets taller and the sides get taller to match. none of the containers (including ViewBox) work exactly this way.
You could use a custom container to do this, but the preferred solution would be to create a behaviour that retains its object's aspect ratio on height changes. We will call it a FixedAspectBehavior:
using System.Windows;
using System.Windows.Interactivity;
namespace SilverlightApplication1
{
public class FixedAspectRatioBehavior : TargetedTriggerAction<FrameworkElement>
{
public double AspectRatio { get; set; }
protected override void OnAttached()
{
base.OnAttached();
FrameworkElement element = this.AssociatedObject as FrameworkElement;
if (element != null)
{
AspectRatio = element.Width/element.Height;
element.SizeChanged += new SizeChangedEventHandler(element_SizeChanged);
}
}
void element_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement element = AssociatedObject as FrameworkElement;
element.Width = element.Height * AspectRatio;
}
protected override void Invoke(object parameter)
{
}
}
}
The layout requires some work as self resizing elements (of type path) do some very weird things depending on their parent container. I had to use ViewBoxes as the parent of the paths, combined with the behaviours to get the desired result:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox>
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 97.1985,101.669L 104.824,95.0005C 105.574,94.3338 106.921,93.8627 107.824,93.9171L 107.824,101.667L 97.1985,101.669 Z " UseLayoutRounding="False" HorizontalAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:FixedAspectRatioBehavior/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Path>
</Viewbox>
<Grid Width="Auto" Background="#FFFF0D0D" Grid.ColumnSpan="1" Grid.Column="1">
<Rectangle Grid.Column="1" Fill="#FFD21F1F"/>
<ContentControl Grid.Column="1" x:Name="HeaderTopSelected" FontSize="{TemplateBinding Control.FontSize}" Foreground="{TemplateBinding Control.Foreground}" IsTabStop="False" Cursor="{TemplateBinding FrameworkElement.Cursor}" HorizontalAlignment="{TemplateBinding FrameworkElement.HorizontalAlignment}" VerticalAlignment="{TemplateBinding FrameworkElement.VerticalAlignment}"/>
<TextBlock TextWrapping="Wrap" FontSize="16"><Run Text="This is just a very string to see "/><LineBreak/><Run Text="what happens when we get to a size where the container should get larger than the default size"/></TextBlock>
</Grid>
<Viewbox Grid.Column="2">
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 118.714,101.678L 111.088,95.0097C 110.338,94.343 108.991,93.8719 108.088,93.9264L 108.088,101.676L 118.714,101.678 Z " UseLayoutRounding="False" d:LayoutOverrides="VerticalAlignment">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:FixedAspectRatioBehavior/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Path>
</Viewbox>
</Grid>
Take 1 (obsolete now, as is 2 below):
The width of a path does not push along items in a stack panel. In that respect it's width is useless except to scale the item.
To get the effect you wanted, group each path into a grid. The size problem then goes away.
Apologies for turning your rectangle horrible red to show it clearly :)
XAML below:
<StackPanel Orientation="Horizontal" Width="Auto">
<Grid>
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 97.1985,101.669L 104.824,95.0005C 105.574,94.3338 106.921,93.8627 107.824,93.9171L 107.824,101.667L 97.1985,101.669 Z " UseLayoutRounding="False" d:LayoutOverrides="Width"/>
</Grid>
<Grid Width="100" Background="#FFFF0D0D">
<Rectangle Grid.Column="1" Fill="#FFD21F1F"/>
<ContentControl Grid.Column="1" x:Name="HeaderTopSelected" FontSize="{TemplateBinding Control.FontSize}" Foreground="{TemplateBinding Control.Foreground}" IsTabStop="False" Cursor="{TemplateBinding FrameworkElement.Cursor}" HorizontalAlignment="{TemplateBinding FrameworkElement.HorizontalAlignment}" VerticalAlignment="{TemplateBinding FrameworkElement.VerticalAlignment}"/>
</Grid>
<Grid>
<Path Stretch="UniformToFill" Fill="#FF000000" Data="F1 M 118.714,101.678L 111.088,95.0097C 110.338,94.343 108.991,93.8719 108.088,93.9264L 108.088,101.676L 118.714,101.678 Z " UseLayoutRounding="False" d:LayoutOverrides="Width"/>
</Grid>
</StackPanel>
Take 2 (also obsolete now, see take 3 at the top):
If you require fixed end widths, use a grid instead of a stack panel (XAML below).
If you require something else, please provide a couple of screen shots of the desired effect.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Path Stretch="Fill" Fill="#FF000000" Data="F1 M 97.1985,101.669L 104.824,95.0005C 105.574,94.3338 106.921,93.8627 107.824,93.9171L 107.824,101.667L 97.1985,101.669 Z " UseLayoutRounding="False" d:LayoutOverrides="Width"/>
<Grid Width="Auto" Background="#FFFF0D0D" Grid.ColumnSpan="1" Grid.Column="1">
<Rectangle Grid.Column="1" Fill="#FFD21F1F"/>
<ContentControl Grid.Column="1" x:Name="HeaderTopSelected" FontSize="{TemplateBinding Control.FontSize}" Foreground="{TemplateBinding Control.Foreground}" IsTabStop="False" Cursor="{TemplateBinding FrameworkElement.Cursor}" HorizontalAlignment="{TemplateBinding FrameworkElement.HorizontalAlignment}" VerticalAlignment="{TemplateBinding FrameworkElement.VerticalAlignment}" Content="Hello there.... this is a long string to see what happens"/>
</Grid>
<Path Stretch="Fill" Fill="#FF000000" Data="F1 M 118.714,101.678L 111.088,95.0097C 110.338,94.343 108.991,93.8719 108.088,93.9264L 108.088,101.676L 118.714,101.678 Z " UseLayoutRounding="False" d:LayoutOverrides="Width" Grid.Column="2"/>
</Grid>

Related

WPF usercontrol combine ellipse and arrow

I want to create a custom usercontrol to represent a player in a 2D map.
I had an ellipse to represent the player but I want to have on the border of the ellipse an arrow to indicate where the player is looking.
This is what I tried :
<Ellipse Width="17" Height="17" Stroke="Black" Fill="White" HorizontalAlignment="Center" />
<Path Data="M5,0 0,5 5,10" Fill="White" Stroke="Black" VerticalAlignment="Center" >
<Path.LayoutTransform>
<RotateTransform Angle="10"/>
</Path.LayoutTransform>
</Path>
the result :
That looks like what I want (it's not properly aligned but that's not the point here).
The problems are :
I know the position of the ellipse's center without the arrow
When the arrow will be on the right the relative position of the ellipse's center will be different --> I could solve this problem using a square control
My Circle has a textblock on top (Horitonzal + vertical center) to
display its id
How to move the arrow depending on the position looked ? I thought the easier might be to calculate an angle and rotate the whole control.
My first idea was to draw using any vector drawing software (illustrator for instance) the path, and get the coordinates of the path, and paste them in WPF.
then just rotate the usercontrol.
But doing this will also rotate the text and I don't want the text to rotate.
I'm stuck on this one, I hope my problem is enough described to be understood.
EDIT My first try :
<ControlTemplate TargetType="ContentControl">
<Grid Width="34" Height="34">
<Path x:Name="contour_forme"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="1"
Stretch="Uniform"
Width="28"
Height="22"
HorizontalAlignment="Left"
Fill="{TemplateBinding Background}"
Data="M28.857,53.500 C24.537,53.487 20.477,52.380 16.938,50.443 C16.938,50.443 16.938,50.500 16.938,50.500 C16.938,50.500 16.785,50.350 16.785,50.350 C12.845,48.157 9.579,44.924 7.317,41.032 C7.317,41.032 -6.176,27.755 -6.176,27.755 C-6.176,27.755 8.206,14.530 8.206,14.530 C10.380,11.316 13.289,8.649 16.681,6.736 C16.681,6.736 16.938,6.500 16.938,6.500 C16.938,6.500 16.938,6.581 16.938,6.581 C20.525,4.615 24.641,3.496 29.021,3.509 C42.835,3.551 53.996,14.775 53.951,28.580 C53.906,42.385 42.670,53.542 28.857,53.500 ZM29.004,8.507 C17.953,8.474 8.965,17.400 8.929,28.443 C8.893,39.487 17.822,48.467 28.873,48.500 C39.924,48.533 48.912,39.608 48.948,28.564 C48.985,17.520 40.056,8.540 29.004,8.507 Z"
>
<Path.LayoutTransform>
<RotateTransform Angle="0" />
</Path.LayoutTransform>
</Path>
<TextBlock Style="{DynamicResource StyleTextes}" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="5"
/>
</Grid>
</ControlTemplate>
With the result :
As you can see I didn't manage to center the text inside my 22px circle.
My arrow is about 6 px height so I've created a control of 22 (circle's size expected) + 2 * 6px depending on the arrow position.
But when I try to rotate my path doing :
<Path.LayoutTransform> <RotateTransform Angle="90" />
</Path.LayoutTransform>
I have the following result :
I'm not sure on how I can keep the circle of my path in the center of the control when I rotate the path.
Just apply the RotateTransform to the "image" but not to the text.
Also I would use a render transform instead of a layout transform.
<Canvas Canvas.Left="206.333" Canvas.Top="119" Height="80" Width="80">
<Path Data="M244,99.333333 L210.16667,109.50034 244.83334,125.50034" Fill="#FFF4F4F5" Stretch="Fill" Stroke="Black" RenderTransformOrigin="0.5,0.5" Height="60" Canvas.Left="3" Canvas.Top="5" Width="60">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="70"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<TextBlock TextWrapping="Wrap" Text="TextBlock" Height="20" Width="80" Canvas.Top="30" TextAlignment="Center"/>
</Canvas>

Position a WPF Path so that its origin is located at the bottom, center of its container

I'm trying to center a Path so that its origin (0, 0) is located at the bottom, center of its container. Assume that the container is a Grid.
Example:
Note: the tail of the arrow is at the origin (0, 0). The tail is centered horizontally but the overall arrow is skewed to the left. This is what I want to achieve regardless of which direction the arrow is pointing.
This needs to work for paths where the x and y coordinates are positive, negative, or a mixture of both.
How can this be done via XAML with the least markup?
Here is what I have been using. Just draw your path wherever you want and afterwards translate it to the desired starting point. You can use Bindings to center it within the grid. This allows to place your geometry wherever you want within your grid.
<Grid>
<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<!-- Your path geomrtry -->
</PathGeometry>
</Path.Data>
<Path.RenderTransform>
<TranslateTransform Y="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Path=ActualHeight, Converter={StaticResource widthAndHeightDivider}}"
X="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Path=ActualWidth, Converter={StaticResource widthAndHeightDivider}}"/>
</Path.RenderTransform>
</path>
</Grid>
And having the following converter, which divides the ActualWidth of the grid by to to get it centered:
public class WidthAndHeightDivider : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value / 2.0;
return d;
}
}
I hope this helps!
Here's what I ended up going with. Seems to work, but if there's a cleaner way to do this, I'd love to hear it:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
StrokeThickness="1"
Data="{Binding PathGeometry}" />
</Grid>
Well... the position en in the bottom-middle depends on the container. Try something like this:
<Grid>
<Path Stroke="Black"
StrokeThickness="1"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Data="M 0,0 L -50,-70 L -50,-80" />
</Grid>

Rectangle Stretch Property Behavior

Can someone explain my misunderstanding of the Rectangle's Stretch property when setting it to NONE? According to MSDN, the definition for this value is "The content preserves its original size." Look at the code below. When I set Stretch = NONE on the fourth rectangle, it disappears.
<Grid Margin="20">
<Rectangle Height="Auto" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch" Fill="Black"/>
<Rectangle Height="2" Width="10" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Black"/>
<Rectangle Height="2" Width="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Fill="Black"/>
<Rectangle Stretch="None" Name="Right" Height="Auto" Width="2" HorizontalAlignment="Right" VerticalAlignment="Stretch" Fill="Black"/>
<Rectangle Height="2" Width="10" HorizontalAlignment="Right" VerticalAlignment="Top" Fill="Black"/>
<Rectangle Height="2" Width="10" HorizontalAlignment="Right" VerticalAlignment="Bottom" Fill="Black"/>
</Grid>
Why is this happening? This code is an excerpt from a paging control I'm using on a custom chart. I am wrapping the paging control in a ViewBox to allow auto-resizing, but I do not want my border markers resizing (the example above is what the page border markers look like).
Rectangle class uses private _rect field for rendering.
Here is the code of the Rectangle.OnRender:
protected override void OnRender(DrawingContext drawingContext)
{
...
drawingContext.DrawRoundedRectangle(base.Fill, pen, this._rect, this.RadiusX, this.RadiusY);
}
Now let's look at the ArrangeOverride method:
protected override Size ArrangeOverride(Size finalSize)
{
...
switch (base.Stretch)
{
case Stretch.None:
{
this._rect.Width = (this._rect.Height = 0.0);
break;
}
...
}
...
return finalSize;
}
It seems that when Stretch in None there is only empty rectangle. You may see it only adding a stroke. Maybe it's a bug.

9-Slice Images in WPF

I was wondering if anyone knew how to duplicate the 9-slice functionality of Flex/Flash in WPF and VB.Net. I have used 9-slice scaling many times in Flex and it would be a great asset in WPF. I would like to be able to have an image as my background of a Canvas and have it stretch without ruining the rounded corners. Please, does anyone know how to do this?
I'm not aware of any built-in functionality that can do this, but you could write a custom control to do so.
The salient part of such a control would be a 9 part grid in which 4 parts were of fixed size (the corners), two parts had fixed heights and variable widths (center top and center bottom), two parts had fixed widths and variable heights (left center and right center), and the final part had variable height and width (the middle). Stretching in only one direction (e.g. making a button that only grows horizontally) is as simply as limiting the middle portion's height.
In XAML, that would be:
<Grid>
<Grid.ColumnDefinitions>
<!-- 20 is the amount of pixel on your image corner -->
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
</Grid>
Then you'd add objects to paint the images on to (I'll use Rectangles), as well as an object to put content into (ContentPresenter):
<Rectangle Grid.Row="0" Grid.Column="0" x:Name="TopLeft"/>
<Rectangle Grid.Row="0" Grid.Column="1" x:Name="TopCenter"/>
<Rectangle Grid.Row="0" Grid.Column="2" x:Name="TopRight"/>
<Rectangle Grid.Row="1" Grid.Column="0" x:Name="CenterLeft"/>
<Rectangle Grid.Row="1" Grid.Column="2" x:Name="CenterRight"/>
<Rectangle Grid.Row="2" Grid.Column="0" x:Name="BottomLeft"/>
<Rectangle Grid.Row="2" Grid.Column="1" x:Name="BottomCenter"/>
<Rectangle Grid.Row="2" Grid.Column="2" x:Name="BottomRight"/>
<Grid Grid.Row="1" Grid.Column="1" x:Name="Middle">
<Rectangle/>
<ContentPresenter x:Name="MiddleContent"/>
</Grid>
Each of the Rectangles can be painted using an ImageBrush so that they show the correct portion of your source image:
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Image.png" TileMode="None"
<!-- Add the settings necessary to show the correct part of the image --> />
</Rectangle.Fill>
</Rectangle>
Wrapping all of that up into a custom control, you could produce a pretty usable 9-slice image control:
<local:NineSliceImage Image="Source.png" Slice="20,20">
<TextBox Text="Nine Slice Image TextBox!"/>
</local:NineSliceImage>
Where Slice is a property of type System.Windows.Size, so that you can use it like the Margin/Padding/etc. properties for setting the position of the slices.
You'll also want to set SnapToDisplayPixels to True on all of the Rectangles; otherwise, you'll see small gaps between the pieces of the image at certain resolutions as WPF tries to interpolate the in-between pixels.
An alternative, slightly faster way of doing this if you plan to use a lot of these controls is to override OnRender and do it there; I've done this in the past for a 3-slice image control, but it's quite a bit more tricky.
That should get you most of the way there - if there's anything I'm missing, leave a comment.
Here is what I ended up getting after some toiling:
This is the SlicedImage.xaml file:
<Rectangle Grid.Row="1" Grid.Column="0" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="CenterLeft" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Left" AlignmentY="Center" />
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="1" Grid.Column="1" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="CenterCenter" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Center" AlignmentY="Center" />
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="1" Grid.Column="2" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="CenterRight" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Right" AlignmentY="Center" />
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="BottomLeft" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Left" AlignmentY="Bottom" />
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="2" Grid.Column="1" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="BottomCenter" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Center" AlignmentY="Bottom" />
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="2" Grid.Column="2" SnapsToDevicePixels="True">
<Rectangle.Fill>
<ImageBrush x:Name="BottomRight" Stretch="Fill" ViewboxUnits="Absolute" AlignmentX="Right" AlignmentY="Bottom" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
And the SlicedImage.xaml.vb for those who want this in VB.Net:
Partial Public Class SlicedImage
Public imageSource As ImageSource
Public sliceTop As Double
Public sliceRight As Double
Public sliceLeft As Double
Public sliceBottom As Double
Public Sub New()
InitializeComponent()
End Sub
Public Sub SetViewboxes()
Dim RealHeight As Double = TopLeft.ImageSource.Height
Dim RealWidth As Double = TopLeft.ImageSource.Width
ColumnLeft.Width = New GridLength(sliceLeft)
ColumnRight.Width = New GridLength(RealWidth - sliceRight)
RowTop.Height = New GridLength(sliceTop)
RowBottom.Height = New GridLength(RealHeight - sliceBottom)
TopLeft.Viewbox = New Rect(0, 0, sliceLeft, sliceTop)
TopCenter.Viewbox = New Rect(sliceLeft, 0, sliceRight - sliceLeft, sliceTop)
TopRight.Viewbox = New Rect(sliceRight, 0, RealWidth - sliceRight, sliceTop)
CenterLeft.Viewbox = New Rect(0, sliceTop, sliceLeft, sliceBottom - sliceTop)
CenterCenter.Viewbox = New Rect(sliceLeft, sliceTop, sliceRight - sliceLeft, sliceBottom - sliceTop)
CenterRight.Viewbox = New Rect(sliceRight, sliceTop, RealWidth - sliceRight, sliceBottom - sliceTop)
BottomLeft.Viewbox = New Rect(0, sliceBottom, sliceLeft, RealHeight - sliceBottom)
BottomCenter.Viewbox = New Rect(sliceLeft, sliceBottom, sliceRight - sliceLeft, RealHeight - sliceBottom)
BottomRight.Viewbox = New Rect(sliceRight, sliceBottom, RealWidth - sliceRight, RealHeight - sliceBottom)
End Sub
Public Property ImageLocation() As ImageSource
Get
Return Nothing
End Get
Set(ByVal value As ImageSource)
TopLeft.ImageSource = value
TopCenter.ImageSource = value
TopRight.ImageSource = value
CenterLeft.ImageSource = value
CenterCenter.ImageSource = value
CenterRight.ImageSource = value
BottomLeft.ImageSource = value
BottomCenter.ImageSource = value
BottomRight.ImageSource = value
End Set
End Property
Public Property Slices() As String
Get
Return Nothing
End Get
Set(ByVal value As String)
Dim sliceArray As Array = value.Split(" ")
sliceTop = sliceArray(0)
sliceRight = sliceArray(1)
sliceBottom = sliceArray(2)
sliceLeft = sliceArray(3)
SetViewboxes()
End Set
End Property
End Class
The User Control would be used like this:
<my:SlicedImage ImageLocation="Images/left_bubble.png" Slices="18 25 19 24" />
where ImageLocation is the image location, and Slices is "top right bottom left" for how the image should be sliced. All dimensions should be based from the top left corner.

Silverlight Scrollviewer With Only Buttons

I am using a ScrollViewer as part of my Silverlight application. It has a horizontal orientation, and I would like it to appear such that only the scroll buttons appear, but not the scroll bar itself. Something like this crude ASCII rendering:
------------------------------------------------------
| | | |
| < | Content Here | > |
| | | |
------------------------------------------------------
I know I could use the templating functionality, but all the samples I've seen only change the look of all the elements, and not their raw positioning, or whether they even appear. Is it possible to do this, and could someone provide an outline of what the template might look like?
Here is another option. Override the default template for SCrollviewer and handle the buttons as PageUp/PageDown. My example below is a scrollviewer that scrolls vertically. You can easily change to to horizontal scrolling and change the handlers from PageUp/PageDown to Left and Right handlers.
<ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ButtonOnlyScrollViewer">
<ControlTemplate.Resources>
<!-- Add style here for repeat button seen below -->
</ControlTemplate.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0"
Foreground="White"
Background="Yellow"
HorizontalAlignment="Stretch"
Command="ScrollBar.PageUpCommand"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}">
</RepeatButton>
<ScrollContentPresenter
CanContentScroll="{TemplateBinding CanContentScroll}"
Grid.Row="1"
Content="{TemplateBinding Content}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Margin="{TemplateBinding Margin}"/>
<RepeatButton Grid.Row="2" Background="Black" Foreground="White" Command="ScrollBar.PageDownCommand">
</RepeatButton>
</Grid>
</ControlTemplate>
I've done something similar and the best way I found to do this was to put your content in a scroll viewer and just turn off the scrollbars. Then code your buttons to scroll the scrollviewer.
Edit: Responding to comment about no way to deal with sizing.
First off, you would build this control as a ContentControl. It should have a template defined in generic.xaml that has your button controls plus the scroll viewer. Something like:
<Canvas x:Name="root">
<Button x:Name="left" Content="<"/>
<Button x:Name="right" Content=">"/>
<ScrollViewer x:Name="viewer" BorderThickness="0" VerticalScrollBarVisibility="Hidden">
<ContentPresenter />
</ScrollViewer>
</Canvas>
Then in your control you would need to override OnApplyTemplate:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
left = GetTemplateChild("left") as Button;
left.Click += new RoutedEvent(YourHandler);
right = GetTemplateChild("right") as Button;
right.Click += new RoutedEvent(YourHandler);
// position your scroll buttons here, not writing that code
scroll = GetTemplateChild("viewer") as ScrollViewer;
root = GetTemplateChild("root") as Canvas;
var fe = this.Content as FrameworkElement;
if (fe != null)
{
fe.SizeChanged += new SizeChangedEventHandler(fe_SizeChanged);
}
}
void fe_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.InvalidateMeasure();
}
protected override Size ArrangeOverride(Size finalSize)
{
if (!double.IsInfinity(scroll.ViewportHeight))
{
left.Visibility = (scroll.HorizontalOffset > 0);
right.Visibility = (scroll.HorizontalOffset < ScrollableHeight);
}
return base.ArrangeOverride(finalSize);
}
protected override Size MeasureOverride(Size availableSize)
{
scroll.Measure(availableSize);
return scroll.DesiredSize;
}
In your button click handlers you would need to (1) scroll the viewer and (2) check the new value of the HorizontalOffset to see if you need to hide or show either of the button.
Disclaimer: This code probably doesn't work as is since it was written by hand and based on a different example.
i found the solution here :)
http://weblogs.asp.net/fredriknormen/archive/2009/09/18/create-an-automatic-scrollable-image-slider-in-silverlight.aspx
This is made using a DispatcherTimer, really nice example :)
I have been searching for working solution for quite a lot of time now. And based on Louis's solution I have managed to make it working. (in WPF)
This solution is for horizontal scrolling.
Firstly, add ListView:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Template>
<ControlTemplate>
<ScrollViewer Template="{StaticResource ButtonOnlyScrollViewer}">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
</ListView>
And a modified template from Louis's answer for horizontal scrolling:
<ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ButtonOnlyScrollViewer">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RepeatButton Content="<"
Grid.Column="0"
Command="ScrollBar.LineLeftCommand"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
<ScrollContentPresenter
CanContentScroll="{TemplateBinding CanContentScroll}"
Grid.Column="1"
Content="{TemplateBinding Content}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Margin="{TemplateBinding Margin}"/>
<RepeatButton Content=">"
Grid.Column="2"
Command="ScrollBar.LineRightCommand"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>

Resources