When showing an XPS document in the DocumentViewer control of a WPF application it does not allow you to scroll its content on a touch enabled tablet just my moving your fingers over the screen.
Instead it selects the text. The only way of scrolling on a touch enabled device is by using the vertical scrollbar.
Is there a way to enable touch scrolling by moving your fingers on the content itself instead of on the vertical scrollbar?
By overriding some styles I could prevent the text selection but it still does not allow me to scroll. ( https://stackoverflow.com/a/415155/187650 )
I had the same problem and i made a solution and it works perfectly.
I made an own Xps Document class to set URI dynamically:
public class ManualXpsDocument : XpsDocument
{
private const string _uriOfDoc= "...\\path.xps";
public ManualXpsDocument(FileAccess fileAccess) : base(_uriOfDoc, fileAccess)
{
}
}
Here is the xaml part of window (or control):
<Grid.Resources>
<ObjectDataProvider x:Key="myDataSource" MethodName="GetFixedDocumentSequence"
ObjectType="{x:Type manual:ManualXpsDocument}">
<ObjectDataProvider.ConstructorParameters>
<io:FileAccess>Read</io:FileAccess>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Grid.Resources>
<DocumentViewer x:Name="Viewer" Document="{Binding Source={StaticResource myDataSource}}">
<DocumentViewer.Resources>
<Style TargetType="ToolBar">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</DocumentViewer.Resources>
<DocumentViewer.Style>
<Style TargetType="{x:Type DocumentViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DocumentViewer}">
<controls:CustomScrollViewer x:Name="PART_ContentHost">
<controls:CustomScrollViewer.Style>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="Focusable" Value="false" />
<Setter Property="IsDeferredScrollingEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Border BorderBrush="#00000000"
BorderThickness="0,2,0,0">
<Grid Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Name="PART_ScrollContentPresenter"
ScrollViewer.IsDeferredScrollingEnabled="True"
KeyboardNavigation.DirectionalNavigation="Local"
CanContentScroll="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<controls:CustomScrollBar Name="PART_VerticalScrollBar"
Style="{DynamicResource ScrollBar}"
Grid.Column="1"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<controls:CustomScrollBar Name="PART_HorizontalScrollBar"
Grid.Row="1"
Grid.ColumnSpan="2"
Style="{DynamicResource ScrollBar}"
Orientation="Horizontal"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</controls:CustomScrollViewer.Style>
</controls:CustomScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DocumentViewer.Style>
</DocumentViewer>
Here is The xaml.cs part of Window (or control):
public ctor()
{
InitializeComponent();
PreviewTouchMove += ScrollingHandler;
}
private void ScrollingHandler(object sender, TouchEventArgs e)
{
TouchPoint tp = e.GetTouchPoint(Viewer);
if (tp.Action == TouchAction.Move)
{
//_scrollingInt is not necessary but Move procedure of Viewer has const value to scroll and the optimalization is recommended.
if (_lastYPosition > tp.Position.Y + _scrollingInt)
{
Viewer.MoveDown();
_lastYPosition = tp.Position.Y;
}
else if (_lastYPosition < tp.Position.Y - _scrollingInt)
{
Viewer.MoveUp();
_lastYPosition = tp.Position.Y;
}
}
// Viewer.IsHitTestVisible = false; this setting can disable the selection too,but if you use this, the hyperlinks won't work too.
Viewer.GetType().GetProperty("IsSelectionEnabled", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(Viewer, false, null);
//set false "IsSelectionEnabled" "hidden" property instead of Viewer.IsHitTestVisible = false, because the hyperlinks will work too.
e.Handled = true;
}
ScrollingHandler method solve the problem. This procedure do the scrolling of document viewer and disable the selection but the hyperlink function still available.
I have a question about the XAML code of the ProgressBar. I have the following XAML code to display the Bar:
<ProgressBar Minimum="0" Maximum="100" Value="80" Style="{StaticResource Progress}" Grid.Row="1" Grid.Column="0" />
The control to display the bar is:
<!-- Progress bar control -->
<Style TargetType="ProgressBar" x:Key="Progress">
<Setter Property="Background" Value="{StaticResource colorProgressbackground}" />
<Setter Property="Foreground" Value="{StaticResource colorProgressactivity}" />
<Setter Property="BorderBrush" Value="{StaticResource colorProgressbackground}" />
<Setter Property="Height" Value="12" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid Name="TemplateRoot"
SnapsToDevicePixels="true">
<Rectangle Fill="{TemplateBinding Background}"
RadiusX="4"
RadiusY="4" />
<Border Background="{StaticResource colorProgressbackground}"
Margin="1"
CornerRadius="2" />
<Decorator Name="PART_Indicator" HorizontalAlignment="Left">
<Grid Name="Foreground">
<Grid Name="Animation" ClipToBounds="True">
<Border Name="PART_GlowRect" Width="{TemplateBinding Value}" BorderThickness="0" CornerRadius="4" Margin="0,0,0,0" HorizontalAlignment="Left" Background="{StaticResource colorProgressactivity}"/>
</Grid>
</Grid>
</Decorator>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, the problem is that the bar is 100% width, but the active bar (the Value-variable in the ProgressBar code) is not 80 percent, but 80 pixels (or so). I cannot give the Gray bar a hardcoded width. How can I make sure that the active bar (PART_indicator) is showing up correctly in percentages?
Thank you in advance!
Looking at the code for the control, it looks like it handles setting the 'Part_Indicator' width automatically.
From reflector
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this._track != null)
{
this._track.SizeChanged -= new SizeChangedEventHandler(this.OnTrackSizeChanged);
}
this._track = base.GetTemplateChild("PART_Track") as FrameworkElement;
this._indicator = base.GetTemplateChild("PART_Indicator") as FrameworkElement;
this._glow = base.GetTemplateChild("PART_GlowRect") as FrameworkElement;
if (this._track != null)
{
this._track.SizeChanged += new SizeChangedEventHandler(this.OnTrackSizeChanged);
}
if (this.IsIndeterminate)
{
this.SetProgressBarGlowElementBrush();
}
}
..
private void SetProgressBarIndicatorLength()
{
if ((this._track != null) && (this._indicator != null))
{
double minimum = base.Minimum;
double maximum = base.Maximum;
double num3 = base.Value;
double num4 = (this.IsIndeterminate || (maximum <= minimum)) ? 1.0 : ((num3 - minimum) / (maximum - minimum));
this._indicator.Width = num4 * this._track.ActualWidth;
this.UpdateAnimation();
}
}
This custom template doesn't seem to have to take the % into account, are you sure that the Part_Indicator is not correctly sizing but you are then sizing the Part_GlowRect incorrectly based on the Value ?
I create a template for buttons:
<Style x:Key="TableButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource GrayBlueGradientBrush}" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="Margin" Value="20" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}" ClipToBounds="True">
<!-- Inner Rectangle with rounded corners. -->
<Rectangle x:Name="innerRectangle"
Stroke="Transparent"
StrokeThickness="20"
Fill="{TemplateBinding Background}"
RadiusX="20" RadiusY="20" />
<Ellipse x:Name="NumberGuests"
Width="25"
Height="25"
Stretch="Fill"
StrokeLineJoin="Round"
Fill="Red"
Margin="-70,-70,0,0"/>
<Ellipse x:Name="NumberChecks"
Width="25"
Height="25"
Stretch="Fill"
StrokeLineJoin="Round"
Fill="Green"
Margin="70,-70,0,0"/>
<Rectangle x:Name="Time"
Width="70"
Height="25"
Fill="White"
RadiusX="10" RadiusY="10"
Margin="0,50,0,0"/>
<TextBlock x:Name='TableID'
FontSize="12"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="0,25,0,0"
Text="Table_New"/>
<TextBlock x:Name='TimeID'
FontSize="12"
HorizontalAlignment="Center"
Margin="0,65,0,0"
Text="Time_New" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Private Sub CreateButton_Click(sender As Object, e As RoutedEventArgs)
Dim TableName As String
Dim TimeNotAvailable As String
LayoutGrid.AllowDragging = True
ToggleButton.Content = "Edit"
Dim LocationButton As New Button
Dim style As Style = TryCast(Me.FindResource("TableButtonStyle"), Style)
TableName = "Test" & I
TimeNotAvailable = "Time" & I
I += 1
LocationButton.Style = style
TableID.Content = TableName
TimeID.Content = TimeNotAvailable
LayoutGrid.Children.Add(LocationButton)
AddHandler LocationButton.Click, AddressOf LocationButtonClicked
End Sub
Every time a "Create Button" button is pressed, a button will be generated based on the template.
However I am not able to set the textblock.text TableID and TimeID.
When creating the buttons I need the tableID and TimeID to get the value of a variable. Like "Table 1", "Table 2" etc.
I tried all different type of bindings, resources etc but binding would change the text in all buttons, first button text would be "Table 1" but when generating the second both buttons would be "Table 2". I also tried DataContext but the both textblocks would have the same data.
I tried creating a button directly in xaml and "TableID.Content = TableName" worked but when using the template "TableID" is not recognized.
Please some assistance. Thanks
As a quick solution you can try this workaround
Add binding to the Text property as follows
<TextBlock x:Name='TableID'
FontSize="12"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="0,25,0,0"
Text="{Binding [0], FallbackValue=Table_New}"/>
<TextBlock x:Name='TimeID'
FontSize="12"
HorizontalAlignment="Center"
Margin="0,65,0,0"
Text="{Binding [1], FallbackValue=Time_New}" />
and replace following
TableID.Content = TableName
TimeID.Content = TimeNotAvailable
with
LocationButton.DataContext = { TableName, TimeNotAvailable }
The above example demonstrates MVVM way of displaying data on the UI.
Explanation
{ TableName, TimeNotAvailable } creates and an implicit array which passed as DataContext to the button then it is used in binding with indexers where [0] is first element and [1] being the second.
I want Image button with two state(normal , mouse over). that button must change image with Mouse Over event trigger automatically.
this image button must be a user control. Also i want to set image for each state form code in which form i use that user control.
Solution is using a template with "Value Converter" but i don't know how?
Why must this image button be a user control? If a regular button with a new control template is fine, this should work:
<Button>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Image Name="HoverImage" Source="hover_image.png" Visibility="Hidden" />
<Image Name="DefaultImage" Source="default_image.png" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="DefaultImage" Property="Visibility" Value="Hidden" />
<Setter TargetName="HoverImage" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
If you need a simple rollover effect, you don't need a template for it.. article below has a solution to it..
http://www.c-sharpcorner.com/Resources/Detail.aspx?ResourceId=706
In this article user uses SolidColorBrush, you can use ImageBrush to set image as background of button.
I found This on Code-project-Article(Cool example)
http://www.codeproject.com/KB/WPF/WPF_xaml_taskbar_window.aspx
First He create Wpf-Custom-control(you can create class inherit from Button like this)
public class ImageButton : Button
{
private string cornerRadius;
public string CornerRadius
{
get { return cornerRadius; }
set { cornerRadius = value; }
}
private string highlightBackground;
public string HighlightBackground
{
get { return highlightBackground; }
set { highlightBackground = value; }
}
private string pressedBackground;
public string PressedBackground
{
get { return pressedBackground; }
set { pressedBackground = value; }
}
}
As second step you must Create template in resource-dictionary(here is code)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Phone.Controls">
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type local:ImageButton}">
<ControlTemplate.Resources>
<Storyboard x:Key="MouseOverButton">
<ThicknessAnimation Storyboard.TargetName="ButtonBackgroundBorder"
Storyboard.TargetProperty="(Control.Margin)"
Duration="0:0:0.05"
FillBehavior="Stop"
From="0,0,0,0" To="2,2,2,2"
AutoReverse="True" />
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="ButtonOuterGrid">
<Border x:Name="ButtonBackgroundBorder"
CornerRadius="{Binding Path=CornerRadius, RelativeSource={RelativeSource TemplatedParent}}"
Background="{Binding Path=HighlightBackground, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="Black"
BorderThickness="0.8"
Opacity="0">
<Border.BitmapEffect>
<OuterGlowBitmapEffect GlowColor="#FFFFFFFF" GlowSize="2.75" Noise="0.20"/>
</Border.BitmapEffect>
</Border>
<Border x:Name="ButtonEdgesBorder" CornerRadius="{Binding Path=CornerRadius, RelativeSource={RelativeSource TemplatedParent}}"
Opacity="1"
BorderBrush="Transparent"
BorderThickness="0" />
<Border x:Name="ButtonContentBorder"
CornerRadius="{Binding Path=CornerRadius, RelativeSource={RelativeSource TemplatedParent}}"
Opacity="1"
BorderThickness="1">
<ContentPresenter x:Name="ContentText"
Width="Auto" Height="Auto"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.Setters>
<Setter Property="Opacity" TargetName="ButtonBackgroundBorder" Value="1"/>
<Setter Property="TextElement.Foreground" TargetName="ContentText" Value="Black"/>
</Trigger.Setters>
</Trigger>
<EventTrigger RoutedEvent="Grid.MouseEnter"
SourceName="ButtonOuterGrid">
<BeginStoryboard Storyboard="{StaticResource MouseOverButton}"/>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="ImageButton" TargetType="{x:Type Button}">
<Setter Property="Template" Value="{StaticResource ButtonTemplate}" />
</Style>
And this is last Step, in xaml file you must insert this custom-control
<ImageButton x:Name="btnConfigs"
Style="{StaticResource ImageButton}"
Width="25" Height="25"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="0,31.125,16.418,0">
<Image x:Name="ImgConfigs"
Stretch="Fill"/>
</ImageButton >
and in cs file do this
this.ImgConfigs.Source="any imag-source"
also we can change this image-source on btnconfig-click event
With special thanks from Murray-Foxcroft for create that article
How to create trapezoid tabs in WPF tab control? I'd like to create non rectangular tabs that look like tabs in Google Chrome or like tabs in code editor of VS 2008.
Can it be done with WPF styles or it must be drawn in code?
Is there any example of code available on internet?
Edit:
There is lots of examples that show how to round corners or change colors of tabs, but I could not find any that changes geometry of tab like this two examples:
VS 2008 Code Editor Tabs
Google Chrome tabs
Tabs in this two examples are not rectangles, but trapezes.
I tried to find some control templates or solutions for this problem on internet, but I didn’t find any “acceptable” solution for me. So I wrote it in my way and here is an example of my first (and last=)) attempt to do it:
<Window x:Class="TabControlTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:TabControlTemplate"
Title="Window1" Width="600" Height="400">
<Window.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FF3164a5" Offset="1"/>
<GradientStop Color="#FF8AAED4" Offset="0"/>
</LinearGradientBrush>
</Window.Background>
<Window.Resources>
<src:ContentToPathConverter x:Key="content2PathConverter"/>
<src:ContentToMarginConverter x:Key="content2MarginConverter"/>
<SolidColorBrush x:Key="BorderBrush" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="HoverBrush" Color="#FFFF4500"/>
<LinearGradientBrush x:Key="TabControlBackgroundBrush" EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#FFa9cde7" Offset="0"/>
<GradientStop Color="#FFe7f4fc" Offset="0.3"/>
<GradientStop Color="#FFf2fafd" Offset="0.85"/>
<GradientStop Color="#FFe4f6fa" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="TabItemPathBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FF3164a5" Offset="0"/>
<GradientStop Color="#FFe4f6fa" Offset="1"/>
</LinearGradientBrush>
<!-- TabControl style -->
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" BorderThickness="2,0,2,2" Panel.ZIndex="2" CornerRadius="0,0,2,2"
BorderBrush="{StaticResource BorderBrush}"
Background="{StaticResource TabControlBackgroundBrush}">
<ContentPresenter ContentSource="SelectedContent"/>
</Border>
<StackPanel Orientation="Horizontal" Grid.Row="0" Panel.ZIndex="1" IsItemsHost="true"/>
<Rectangle Grid.Row="0" Height="2" VerticalAlignment="Bottom"
Fill="{StaticResource BorderBrush}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TabItem style -->
<Style x:Key="{x:Type TabItem}" TargetType="{x:Type TabItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Grid x:Name="grd">
<Path x:Name="TabPath" StrokeThickness="2"
Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}"
Stroke="{StaticResource BorderBrush}"
Fill="{StaticResource TabItemPathBrush}">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="1,0"
Segments="{Binding ElementName=TabItemContent, Converter={StaticResource content2PathConverter}}">
</PathFigure>
</PathGeometry>
</Path.Data>
<Path.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Path.LayoutTransform>
</Path>
<Rectangle x:Name="TabItemTopBorder" Height="2" Visibility="Visible"
VerticalAlignment="Bottom" Fill="{StaticResource BorderBrush}"
Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}" />
<ContentPresenter x:Name="TabItemContent" ContentSource="Header"
Margin="10,2,10,2" VerticalAlignment="Center"
TextElement.Foreground="#FF000000"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="grd">
<Setter Property="Stroke" Value="{StaticResource HoverBrush}" TargetName="TabPath"/>
</Trigger>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Fill" TargetName="TabPath">
<Setter.Value>
<SolidColorBrush Color="#FFe4f6fa"/>
</Setter.Value>
</Setter>
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect Direction="302" Opacity="0.4"
ShadowDepth="2" Softness="0.5"/>
</Setter.Value>
</Setter>
<Setter Property="Panel.ZIndex" Value="2"/>
<Setter Property="Visibility" Value="Hidden" TargetName="TabItemTopBorder"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="20">
<TabControl Grid.Row="0" Grid.Column="1" Margin="5" TabStripPlacement="Top"
Style="{StaticResource TabControlStyle}" FontSize="16">
<TabItem Header="MainTab">
<Border Margin="10">
<TextBlock Text="The quick brown fox jumps over the lazy dog."/>
</Border>
</TabItem>
<TabItem Header="VeryVeryLongTab" />
<TabItem Header="Tab" />
</TabControl>
</Grid>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace TabControlTemplate
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
}
}
public class ContentToMarginConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new Thickness(0, 0, -((ContentPresenter)value).ActualHeight, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class ContentToPathConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var ps = new PathSegmentCollection(4);
ContentPresenter cp = (ContentPresenter)value;
double h = cp.ActualHeight > 10 ? 1.4 * cp.ActualHeight : 10;
double w = cp.ActualWidth > 10 ? 1.25 * cp.ActualWidth : 10;
ps.Add(new LineSegment(new Point(1, 0.7 * h), true));
ps.Add(new BezierSegment(new Point(1, 0.9 * h), new Point(0.1 * h, h), new Point(0.3 * h, h), true));
ps.Add(new LineSegment(new Point(w, h), true));
ps.Add(new BezierSegment(new Point(w + 0.6 * h, h), new Point(w + h, 0), new Point(w + h * 1.3, 0), true));
return ps;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
These two converters I wrote to adjust tab size to its content. Actually, I making Path object depending on content size. If you don’t need tabs with various widths, you can use some modified copy of this:
<Style x:Key="tabPath" TargetType="{x:Type Path}">
<Setter Property="Stroke" Value="Black"/>
<Setter Property="Data">
<Setter.Value>
<PathGeometry Figures="M 0,0 L 0,14 C 0,18 2,20 6,20 L 60,20 C 70,20 80,0 84,0"/>
</Setter.Value>
</Setter>
</Style>
screen:
sample project(vs2010)
Note: This is only an appendix to rooks' great answer.
While rooks' solution was working perfectly at runtime for me I had some trouble when opening the MainWindow in the VS2010 WPF designer surface: The designer threw exceptions and didn't display the window. Also the whole ControlTemplate for TabItem in TabControl.xaml had a blue squiggle line and a tooltip was telling me that a NullReferenceException occurred. I had the same behaviour when moving the relevant code into my application. The problems were on two different machines so I believe it wasn't related to any problems of my installation.
In case that someone experiences the same issues I've found a fix so that the example works now at runtime and in the designer as well:
First: Replace in the TabControl-XAML code ...
<Path x:Name="TabPath" StrokeThickness="2"
Margin="{Binding ElementName=TabItemContent,
Converter={StaticResource content2MarginConverter}}"
Stroke="{StaticResource BorderBrush}"
Fill="{StaticResource TabItemPathBrush}">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="1,0"
Segments="{Binding ElementName=TabItemContent,
Converter={StaticResource content2PathConverter}}">
</PathFigure>
</PathGeometry>
</Path.Data>
<Path.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Path.LayoutTransform>
</Path>
... by ...
<Path x:Name="TabPath" StrokeThickness="2"
Margin="{Binding ElementName=TabItemContent,
Converter={StaticResource content2MarginConverter}}"
Stroke="{StaticResource BorderBrush}"
Fill="{StaticResource TabItemPathBrush}"
Data="{Binding ElementName=TabItemContent,
Converter={StaticResource content2PathConverter}}">
<Path.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Path.LayoutTransform>
</Path>
Second: Replace at the end of the Convert method of the ContentToPathConverter class ...
return ps;
... by ...
PathFigure figure = new PathFigure(new Point(1, 0), ps, false);
PathGeometry geometry = new PathGeometry();
geometry.Figures.Add(figure);
return geometry;
I have no explanation why this runs stable in the designer but not rooks' original code.
I just finished a Google Chrome-like Tab Control for WPF. You can find the project at https://github.com/realistschuckle/wpfchrometabs and blog posts describing it at
Google Chrome-Like WPF Tab Control
ChromeTabControl and Visual Children in WPF
WPF Chrome Tabs Functioning
Hope that helps you get a better understanding of building a custom tab control from scratch.
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TabControl}">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style>
<Setter Property="Control.Height" Value="20"></Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Margin="0 0 -10 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10">
</ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Path Data="M10 0 L 0 20 L 10 20 " Fill="{TemplateBinding Background}" Stroke="Black"></Path>
<Rectangle Fill="{TemplateBinding Background}" Grid.Column="1"></Rectangle>
<Rectangle VerticalAlignment="Top" Height="1" Fill="Black" Grid.Column="1"></Rectangle>
<Rectangle VerticalAlignment="Bottom" Height="1" Fill="Black" Grid.Column="1"></Rectangle>
<ContentPresenter Grid.Column="1" ContentSource="Header" />
<Path Data="M0 20 L 10 20 L0 0" Fill="{TemplateBinding Background}" Grid.Column="2" Stroke="Black"></Path>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Background" Value="Beige"></Setter>
<Setter Property="Panel.ZIndex" Value="1"></Setter>
</Trigger.Setters>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Trigger.Setters>
<Setter Property="Background" Value="LightGray"></Setter>
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<TabControl>
<TabItem Header="One" ></TabItem>
<TabItem Header="Two" ></TabItem>
<TabItem Header="Three" ></TabItem>
</TabControl>
</Grid>
I know this is old but I'd like to propose:
XAML:
<Window.Resources>
<ControlTemplate x:Key="trapezoidTab" TargetType="TabItem">
<Grid>
<Polygon Name="Polygon_Part" Points="{Binding TabPolygonPoints}" />
<ContentPresenter Name="TabContent_Part" Margin="{TemplateBinding Margin}" Panel.ZIndex="100" ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="False">
<Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/>
<Setter TargetName="Polygon_Part" Property="Fill" Value="DimGray" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Polygon_Part" Property="Fill" Value="Goldenrod" />
<Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Panel.ZIndex" Value="90"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="100"/>
<Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/>
<Setter TargetName="Polygon_Part" Property="Fill" Value="LightSlateGray "/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<!-- Test the tabs-->
<TabControl Name="FruitTab">
<TabItem Header="Apple" Template="{StaticResource trapezoidTab}" />
<TabItem Margin="-8,0,0,0" Header="Grapefruit" Template="{StaticResource trapezoidTab}" />
<TabItem Margin="-16,0,0,0" Header="Pear" Template="{StaticResource trapezoidTab}"/>
</TabControl>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Media;
namespace TrapezoidTab
{
public class TabHeaderViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _tabHeaderText;
private List<Point> _polygonPoints;
private PointCollection _pointCollection;
public TabHeaderViewModel(string tabHeaderText)
{
_tabHeaderText = tabHeaderText;
TabPolygonPoints = GenPolygon();
}
public PointCollection TabPolygonPoints
{
get { return _pointCollection; }
set
{
_pointCollection = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("TabPolygonPoints"));
}
}
public string TabHeaderText
{
get { return _tabHeaderText; }
set
{
_tabHeaderText = value;
TabPolygonPoints = GenPolygon();
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("TabHeaderText"));
}
}
private PointCollection GenPolygon()
{
var w = new FormattedText(_tabHeaderText, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Tahoma"), 12, Brushes.Black);
var width = w.Width + 30;
_polygonPoints = new List<Point>(4);
_pointCollection = new PointCollection(4);
_polygonPoints.Add(new Point(2, 21));
_polygonPoints.Add(new Point(10, 2));
_polygonPoints.Add(new Point(width, 2));
_polygonPoints.Add(new Point(width + 8, 21));
foreach (var point in _polygonPoints)
_pointCollection.Add(point);
return _pointCollection;
}
}
}
Main:
namespace TrapezoidTab
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
foreach (var obj in FruitTab.Items)
{
var tab = obj as TabItem;
if (tab == null) continue;
tab.DataContext = new TabHeaderViewModel(tab.Header.ToString());
}
}
}
}
yep, you can do that--basically all you have to do is make a custom control-template. Check out http://www.switchonthecode.com/tutorials/the-wpf-tab-control-inside-and-out
(Dead link. WaybackMachine redirects here)
for a tutorial. Just googling "wpf" "tabcontrol" "shape" turns up pages of results.
I have not tried this myself, but you should be able to replace the tag(s) in the template with tags to get the shape that you want.
To slope both left and right tab edges, here is a modification of Slauma's enhancement to rook's accepted answer. This is a replacement of Convert method of the ContentToPathConverter class:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var ps = new PathSegmentCollection(4);
ContentPresenter cp = (ContentPresenter)value;
double h = cp.ActualHeight > 10 ? 1.4 * cp.ActualHeight : 10;
double w = cp.ActualWidth > 10 ? 1.25 * cp.ActualWidth : 10;
// Smaller unit, so don't need fractional multipliers.
double u = 0.1 * h;
// HACK: Start before "normal" start of tab.
double x0 = -4 * u;
// end of transition
double x9 = w + 8 * u;
// transition width
double tw = 8 * u;
// top "radius" (actually, gradualness of curve. Larger value is more rounded.)
double rt = 5 * u;
// bottom "radius" (actually, gradualness of curve. Larger value is more rounded.)
double rb = 3 * u;
// "(x0, 0)" is start point - defined in PathFigure.
// Cubic: From previous endpoint, 2 control points + new endpoint.
ps.Add(new BezierSegment(new Point(x0 + rb, 0), new Point(x0 + tw - rt, h), new Point(x0 + tw, h), true));
ps.Add(new LineSegment(new Point(x9 - tw, h), true));
ps.Add(new BezierSegment(new Point(x9 - tw + rt, h), new Point(x9 - rb, 0), new Point(x9, 0), true));
// "(x0, 0)" is start point.
PathFigure figure = new PathFigure(new Point(x0, 0), ps, false);
PathGeometry geometry = new PathGeometry();
geometry.Figures.Add(figure);
return geometry;
}
Also in TabControl's ControlTemplate, add left (and optionally right) margins to the container of the tab items (the only change is added Margin="20,0,20,0"):
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
...
<Setter Property="Template">
...
<StackPanel Grid.Row="0" Panel.ZIndex="1" Orientation="Horizontal" IsItemsHost="true" Margin="20,0,20,0"/>
Issue: There is a slight visual "glitch" at bottom of left edge of tab, when it is not the selected tab. I think it is related to "going backwards" to start before beginning of tab area. Or else related to drawing of line line at bottom of tab (which doesn't know we start before the left edge of the tabs "normal" rectangle).