(For the back story about this question please see Question Here)
To continue learning about WPF I am trying to Extend an TextBox Control and add a Dependency Property which can be used in a Style to put a border around my extended TextBox Control. To help me achieve this I am using this article basics-of-extending-a-wpf-control.
I have encountered two problems:
1. When I put the style into a Resource Dictionary and try to merge the Resource Dictionary with a file called "generic.xaml" (also tried "Generic.xaml") the style does not take, upon reading this question here trouble-referencing-a-resource-dictionary-that-contains-a-merged-dictionary I tried the solution of adding a Style referencing any control but it did not work (all code at the bottom of the question}. Can anyone explain why please?
2. To overcome the problem above I moved my Style into a Window Resource, but I encounted a different problem. I purposely left out the following code from the worked example in my Constructor of my Extended Control
static ExTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExTextBox),
new FrameworkPropertyMetadata(typeof(ExTextBox)));
}
as I was expecting my control to inherit the default style of the Standard TextBox, which includes the Properties BorderThickness and BorderBrush. However, when I try and Set these Properties with the Setter statement vs2010 says it can not find the Properties. Can someone explain why and if these Properties are not inherited do I have to use a Control Template in my Style to set up a Border around my Control?
My code from problem 1
MainWindow.xaml
<Window x:Class="Controls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleTriggersSample" Height="100" Width="300" xmlns:my="clr-namespace:Controls">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="138*" />
<ColumnDefinition Width="140*" />
</Grid.ColumnDefinitions>
<my:ExTextBox Height="23" HorizontalAlignment="Left" Margin="12,28,0,0" x:Name="exTextBox1" Text="ExTextBlock" VerticalAlignment="Top" Width="116" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Margin="14,30,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="textBox1" />
<Button Content="Test" Grid.Column="1" Height="23" HorizontalAlignment="Left" Margin="14,0,0,0" Name="button1" VerticalAlignment="Top" Width="114" Click="button1_Click" />
</Grid>
My Extended Control
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Controls
{
public class ExTextBox : TextBlock
{
public static DependencyProperty ErrorFlagProperty =
DependencyProperty.Register("ErrorFlag", typeof(bool),
typeof(ExTextBox),
new FrameworkPropertyMetadata((bool)false) );
public bool ErrorFlag
{
get { return (bool)GetValue(ErrorFlagProperty); }
set { SetValue(ErrorFlagProperty, value); }
}
static ExTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExTextBox),
new FrameworkPropertyMetadata(typeof(ExTextBox)));
}
}
}
Controls.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Controls:ExTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="FontSize" Value="28" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="Silver" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="120" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="BorderThickness"
Value="5" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/TestContentStyle;component/Controls.xaml" />
</ResourceDictionary.MergedDictionaries>-->
<!-- Dummy Style, anything you won't use goes -->
<Style TargetType="{x:Type Rectangle}" />
Related
Summary
When assigning a style's x:Key to a static ResourceKey, the XAML intellisense locks up the autocomplete field if the item is included. The item appears as a blank (no name) item and causes a lock-up of pressing any keyboard keys while within the autocomplete box besides Arrow Keys and Delete. Can't press Enter, can't continue typing the name of the resource, just have to paste it in from somewhere else or double-click it in the list. This is obviously very annoying.
I've tried this on 2 computers (1 without any extensions installed) and in Visual Studio 2015 and 2013 and tinkered with various Text Editor settings, but no good. I recreated the project from scratch to see if it was just bad caching data or something, but still no good. People seemed to have similar issues when using Resharper, but disabling that and clearing the cache on the computer that did have Resharper installed didn't fix it either.
Since I know this is a common method as to how to assign ResourceKey's to templates/styles and I can't find anything on my specific problem, I figure I must be doing something wrong here. But no matter how simple I made the project or how many small changes I made to it, the problem persisted.
Code
MainWindow.xaml
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:theme="clr-namespace:WpfTestApp.Theme"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="{x:Static theme:SharedStyleKeys.ButtonBaseStyleKey}" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Height" Value="24" />
</Style>
<Style x:Key="ButtonBaseStyleKeyThatWorks" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="Height" Value="24" />
</Style>
</Window.Resources>
<StackPanel>
<Button Style="{StaticResource ButtonBaseStyleKeyThatWorks}" />
<Button Style="{StaticResource {x:Static theme:SharedStyleKeys.ButtonBaseStyleKey}}" />
<Button Style="{StaticResource <!--Type here-->}" />
</StackPanel>
</Window>
Theme/SharedStyleKeys.cs
using System.Windows;
namespace WpfTestApp.Theme
{
public static class SharedStyleKeys
{
private static ComponentResourceKey buttonBaseStyleKey = new ComponentResourceKey(typeof(SharedStyleKeys), (object)"ButtonBaseStyleKey");
public static ResourceKey ButtonBaseStyleKey
{
get { return buttonBaseStyleKey; }
}
}
}
I have an issue with ListBoxItems. I am trying to make all controls in the ListBoxItem select it as well, so clicking on a TextBox, Label, etc will select the ListBoxItem. Pretty simple so far.
I am also changing the ListBoxItem Template to change the selection visualization from highlighting the background to just drawing a border. Also pretty simple.
The combination of these two, however, seems to cause some really irritating issues with MouseDown and PreviewMouseDown, specifically in my case regarding Labels in a Grid, where one creates a "void" occupied by Grid space.
Using snoop, I can see the PreviewMouseDown event stopping at the ScrollViewer inside the ListBox, and not going all the way to the ListBoxItem.
XAML:
<Window x:Class="ListBoxClickThroughTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ListBox ItemsSource="{Binding Items}"
SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Name="VerySuperLongLabel"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
Content="VerySuperLongLabel"
Padding="0" />
<TextBox Name="Textbox1"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox1 Text" />
<Label Name="ShortLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Content="ShortLabel"
Padding="0" />
<TextBox Name="Textbox2"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox2 Text" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<EventSetter Event="MouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd"
BorderThickness="1">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="BorderBrush" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ListBoxClickThroughTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new List<string>() { "1", "2" };
InitializeComponent();
DataContext = this;
}
public List<string> Items { get; set; }
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var listBoxItem = (ListBoxItem)sender;
listBoxItem.IsSelected = true;
}
}
}
However, if I remove the Template setter, all is well. Is there some magic in the template I'm missing? I tried renaming the border to "Bd" as that was what the default template border was named, but no luck. Any ideas?
If you change the horizontal alignment of the labels from "Left" to "Stretch" this will fix the problem and keep the visual formatting the same.
Mousedown events only work in areas where elements exist. By having the labels at a "left" horizontal alignment, you are creating the "void" you mentioned, where no element exists at that level that can be clicked. To visually see the difference, try temporarily setting the background property of the label elements that are giving you problems, and you'll see the element doesn't extend all the way to the textbox.
I was trying to make re-usable tamplete for image button, and found this great advice, but I wasn't able to make it work. Can you please advice my, what am I doing wrong? I am pretty newbie with WPF, so it may be something really basic, but I can't see it.
My ImageButton.cs class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ImageBut
{
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton),
new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(default(ImageSource)));
}
}
My generic.xaml in Themes subfolder.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ImageBut;assembly=ImageBut"
>
<Style x:Key="{x:Type my:ImageButton}" TargetType="{x:Type my:ImageButton}">
<Setter Property="Width" Value="32"></Setter>
<Setter Property="Height" Value="32"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="Margin" Value="4,0,0,0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Image Source="{TemplateBinding Image}" Stretch="Fill"/>
</Grid>
<ControlTemplate.Triggers>
<!-- Some triggers ( IsFocused, IsMouseOver, etc.) -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
My MainWindow.xaml
<Window x:Class="ImageBut.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ImageBut;assembly=ImageBut"
Title="MainWindow" Height="350" Width="525">
<Grid>
<my:ImageButton Image="C:\Users\Martin\Source\Workspaces\Digiterm\ImageBut\ImageBut\1_1.bmp"></my:ImageButton>
</Grid>
</Window>
Here are my Project properties and here is snapchat of Solution Explorer.
Exception I am getting:
1>C:\Users\Martin\Source\Workspaces\Digiterm\ImageBut\ImageBut\MainWindow.xaml(7,10): error MC3074: The tag 'ImageButton' does not exist in XML namespace 'clr-namespace:ImageBut;assembly=ImageBut'.
Thank you!
You shouldn't need the assembly name in the namespace declaration.
Instead of this:
xmlns:my="clr-namespace:ImageBut;assembly=ImageBut"
try this:
xmlns:my="clr-namespace:ImageBut"
As an aside, the standard WPF Button control supports any kind of content, including images. So you can have an Image control inside a Button, like so:
<Button>
<Image Source="C:\Users\Martin\Source\Workspaces\Digiterm\ImageBut\ImageBut\1_1.bmp" />
</Button>
EDIT
In your generic.xaml file you have set the TargetType of the ControlTemplate to the wrong thing, which is causing a compile-time error. Instead of:
<ControlTemplate TargetType="{x:Type Button}">
it should be:
<ControlTemplate TargetType="{x:Type my:ImageButton}">
After fixing this, the project compiled successfully on my computer.
I need help because I don't understand why the controls coming from a datatemplate doesn't inherit the style defined in the window resources.
May be is there a workaround?
I would be very thankful if someone could give me a solution because I've spent a lots of time to find something.
Hereby my exmaple. For instance the Texblock in horrizontal Template is not align:
Udapte :
I have added background colors. The style is applied to label but not totextblock and textbox defined by the datatemplate.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localview="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="{x:Type TextBlock}" TargetType="TextBlock" >
<Setter Property="Background" Value="Cyan"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="FontFamily" Value="Comic Sans MS"/>
</Style>
<Style x:Key="{x:Type Label}" TargetType="Label">
<Setter Property="Background" Value="Red"/>
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="{x:Type TextBox}" TargetType="TextBox">
<Setter Property="Background" Value="Cyan"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="3"/>
</Style>
<Style x:Key="{x:Type ComboBox}" TargetType="ComboBox">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="3"/>
</Style>
<localview:TemplateSelector x:Key="TemplateSelector">
<localview:TemplateSelector.DataTemplateH>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="Value"/>
<TextBox Text="{Binding Path=SelectedItem.Content ,ElementName=Combo}"/>
</StackPanel>
</DataTemplate>
</localview:TemplateSelector.DataTemplateH>
<localview:TemplateSelector.DataTemplateV>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Label Content="Value"/>
<StackPanel Orientation="Horizontal">
<Label Content="new line"/>
**<TextBlock Text="{Binding Path=SelectedItem.Content ,ElementName=Combo}" TextAlignment="Right"/>**
</StackPanel>
</StackPanel>
</DataTemplate>
</localview:TemplateSelector.DataTemplateV>
</localview:TemplateSelector>
</Window.Resources>
<StackPanel Orientation="Vertical">
<StackPanel>
<TextBlock Text="Texblock"/>
<TextBox Text="Texblock"/>
<StackPanel Orientation="Horizontal">
<Label Content="Value"/>
<ComboBox Name="Combo">
<ComboBox.Items>
<ComboBoxItem Content="H"/>
<ComboBoxItem Content="V"/>
</ComboBox.Items>
</ComboBox>
</StackPanel>
<ContentControl ContentTemplateSelector="{StaticResource TemplateSelector}"
Content="{Binding Path=SelectedItem.Content ,ElementName=Combo}" />
</StackPanel>
</StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Reflection;
namespace WpfApplication3
{
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplateH
{
get;
set;
}
public DataTemplate DataTemplateV
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
string s = (string)item;
if (s == "H")
return DataTemplateH;
if (s == "V")
return DataTemplateV;
return base.SelectTemplate(item, container);
}
}
}
Just to shed some light on why the TextBlock doesn't find its implicit style, there is a curious rule in WPF implicit styles are only inherited across template boundaries by elements which inherit from the Control class; elements which do not inherit from Control will not probe for implicit styles outside of the parent template.
The code responsible for this can be found in FrameworkElement:
// FindImplicitSytle(fe) : Default: unlinkedParent, deferReference
internal static object FindImplicitStyleResource(
FrameworkElement fe,
object resourceKey,
out object source)
{
...
// For non-controls the implicit StyleResource lookup must stop at
// the templated parent. Look at task 25606 for further details.
DependencyObject boundaryElement = null;
if (!(fe is Control))
{
boundaryElement = fe.TemplatedParent;
}
...
}
Carole Snyder at Microsoft explains the reasons for this behavior:
The reason I was given is that Controls are more obvious than elements, and it's likely that an implicit style for a control should be applied everywhere, where it is less likely that a implicit style for an element should be universally applied. There's a legitimate point to this argument. Consider the following:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="16"/>
<Setter Property="Foreground" Value="Green"/>
</Style>
</StackPanel.Resources>
<TextBlock HorizontalAlignment="Center" Text="Hello!"/>
<Button Content="Click me!" Width="200"/>
<TextBlock HorizontalAlignment="Center" Text="Please click the button"/>
</StackPanel>
A Button displays strings by eventually creating a TextBlock and adding the string to the TextBlock. If the TextBlock in the Button used implicit styles defined by the application, the XAML would render this way:
That probably isn't the behavior you want. On the other hand, suppose you're creating a cool UI and you want all of your RepeatButtons to have a specific look. If you define the appearance of the RepeatButton once, all RepeatButtons will use have that appearance, even if the RepeatButton is inside a ControlTemplate.
I've just tried some simple demos and yes the answer is you cannot apply default Style defined somewhere outside the template to some TextBlock inside the template (including both DataTemplate and ControlTemplate). This does not happen to other controls such as Label, TextBox (although you also said the Style did not apply on the TextBox, but I tried it and actually that's not true).
To fix the issue, the best way is set the Style explicitly for the TextBlock something like this:
<TextBlock Text="{Binding Path=SelectedItem.Content ,ElementName=Combo}"
TextAlignment="Right" Style="{StaticResource {x:Type TextBlock}}"/>
Note that as I said, it's required only for TextBlocks inside template (DataTemplate and ControlTemplate).
The code looks fairly ridiculous but it actually works, without doing that way as you see it won't work.
A few options without making the style explicit:
If you want the styles to apply everywhere, put them in App.xaml. Implicit style lookup checks App.xaml even when blocked elsewhere.
If not, you can move the styles into a ResourceDictionary XAML file and merge it into the DataTemplate's resources (bypassing the inheritance block), and wherever else you want them.
If you truly want outside styles to inherit into the templates, you can use C# to add the control's resources to its DataTemplate's resources, bypassing the inheritance block. Something like: dataTemplate.Resources.MergedDictionaries.Add(control.Resources).
Big thanks to Mike's great answer for saving my sanity as to why TextBlock is special.
Here's how I want to summarize it:
Implicit styles are special when they target elements that don't inherit from Control (TextBlock, Ellipse, etc.). These styles are not inherited from outside of a control into its template unless they are defined in (or merged into) the control's resources or App.xaml. In the former case, they affect the control template but not header or content templates. In the latter, they affect all three.
Is there something standard and similar to the Windows Forms SplitContainer in WPF?
I'm a bit lost with Grids because controls seem not to be inside the cells but over them :s.
I did some googling but I don't know exactly what to write in the search field...
Sample of how to make an Horizontal SplitContainer using a Grid and a GridSplitter like SnowBear said:
<Window x:Class="JobFinder1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="JobFinder1" Height="350" Width="525" AllowsTransparency="False">
<Grid Name="gridMain">
<Grid.RowDefinitions>
<RowDefinition Height="26" />
<RowDefinition />
<RowDefinition Height="1"/>
<RowDefinition />
</Grid.RowDefinitions>
<ToolBar Height="26" VerticalAlignment="Stretch" HorizontalAlignment="Left" />
<ListView Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<GridSplitter Background="DarkGray" ResizeDirection="Rows" Grid.Row="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ResizeBehavior="PreviousAndNext" />
<ListView Grid.Row="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</Window>
Grid + GridSplitter at your service
Grid + GridSplitter are a pain. Well they're easy but kinda make for ugly Xaml and sometimes hard to maintain if you're in the design phase and rearranging things a lot.
You may be looking for something like this
http://wpfsplitcontainer.codeplex.com/
Note: check the license before using
Here's a simple control, needs a bit more love, but hey, does it's
job ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;
namespace Infinity.Shell.Controls.Docking
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:Infinity.Shell.Controls.Docking"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:Infinity.Shell.Controls.Docking;assembly=Infinity.Shell.Controls.Docking"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Browse to and select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:SplitContainer/>
///
/// </summary>
public class SplitContainer : Control
{
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(SplitContainer), new PropertyMetadata(Orientation.Horizontal));
public UIElement FirstChild
{
get { return (UIElement)GetValue(FirstChildProperty); }
set { SetValue(FirstChildProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FirstChildProperty =
DependencyProperty.Register("FirstChild", typeof(UIElement), typeof(SplitContainer), new PropertyMetadata(null));
public UIElement SecondChild
{
get { return (UIElement)GetValue(SecondChildProperty); }
set { SetValue(SecondChildProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SecondChildProperty =
DependencyProperty.Register("SecondChild", typeof(UIElement), typeof(SplitContainer), new PropertyMetadata(null));
static SplitContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitContainer), new FrameworkPropertyMetadata(typeof(SplitContainer)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
}
}
<Style TargetType="{x:Type dock:SplitContainer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type dock:SplitContainer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <!--Horizontal -->
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid x:Name="PART_FirstChildGrid"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="3">
<ContentPresenter Content="{TemplateBinding FirstChild}"/>
</Grid>
<GridSplitter x:Name="PART_Splitter"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="3"
Grid.ColumnSpan="1"
Width="5"
VerticalAlignment="Stretch"
HorizontalAlignment="Center"
ShowsPreview="True"/>
<Grid x:Name="PART_SecondChildGrid"
Grid.Column="2"
Grid.Row="0"
Grid.RowSpan="3">
<ContentPresenter Content="{TemplateBinding SecondChild}"/>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter TargetName="PART_FirstChildGrid" Property="Grid.Column" Value="0"/>
<Setter TargetName="PART_FirstChildGrid" Property="Grid.Row" Value="0"/>
<Setter TargetName="PART_FirstChildGrid" Property="Grid.ColumnSpan" Value="3"/>
<Setter TargetName="PART_FirstChildGrid" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="PART_Splitter" Property="Grid.Column" Value="0"/>
<Setter TargetName="PART_Splitter" Property="Grid.Row" Value="1"/>
<Setter TargetName="PART_Splitter" Property="Grid.ColumnSpan" Value="3"/>
<Setter TargetName="PART_Splitter" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="PART_Splitter" Property="VerticalAlignment" Value="Center"/>
<Setter TargetName="PART_Splitter" Property="HorizontalAlignment" Value="Stretch"/>
<Setter TargetName="PART_Splitter" Property="Width" Value="Auto"/>
<Setter TargetName="PART_Splitter" Property="Height" Value="5"/>
<Setter TargetName="PART_SecondChildGrid" Property="Grid.Column" Value="0"/>
<Setter TargetName="PART_SecondChildGrid" Property="Grid.Row" Value="2"/>
<Setter TargetName="PART_SecondChildGrid" Property="Grid.ColumnSpan" Value="3"/>
<Setter TargetName="PART_SecondChildGrid" Property="Grid.RowSpan" Value="1"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Infinity.Shell.Controls.Text;assembly=Infinity.Shell"
xmlns:dock="clr-namespace:Infinity.Shell.Controls.Docking;assembly=Infinity.Shell"
x:Class="Infinity.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="499.573" Background="#FF1E1E1E">
<Grid>
<dock:SplitContainer Orientation="Vertical">
<dock:SplitContainer.FirstChild>
<dock:SplitContainer>
<dock:SplitContainer.FirstChild>
<Button Content="First Child Button"/>
</dock:SplitContainer.FirstChild>
<dock:SplitContainer.SecondChild>
<Button Content="Second Child Button"/>
</dock:SplitContainer.SecondChild>
</dock:SplitContainer>
</dock:SplitContainer.FirstChild>
<dock:SplitContainer.SecondChild>
<Button Content="First Splitter Second Child"/>
</dock:SplitContainer.SecondChild>
</dock:SplitContainer>
</Grid>
</Window>
Another one, without GPL license
http://www.codeproject.com/Articles/293396/Teh-Simple-but-Useful-WPF-Container
This GridSplitter tutorial from Microsoft is also very handy. Here is an expanded example, with both horizontal and vertical resizing. (Pay attention to GridSplitter's HorizontalAligment and ResizeDirection).
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition MinHeight="70" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="70" />
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0" Background="LightBlue" ></DockPanel>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" ResizeDirection="Columns" Height="Auto" Width="3" />
<Grid Grid.Column="2" Background="PaleGoldenrod" ></Grid>
</Grid>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" ResizeDirection="Rows" Height="3" Width="Auto" />
<DockPanel Grid.Row="2" Background="Green" ></DockPanel>
</Grid>