简单但有用的 WPF 容器
利用 Grid 和 GridSplitter 组合成 SplitContainer
- 张戈登
引言
多年来,我从 CodeProject 中受益匪浅,现在是时候贡献一点力量了,希望我的成功和错误能对他人有所帮助。我才开始研究 WPF 不到一个月,所以可能有很多错误。因此,请自行承担风险免费使用。但任何评论或改进都将不胜感激。
废话不多说…
互联网上已经有 2、3 个不错的 WPF SplitContainer
,其中一个 来自开源 CodePlex,你可以从中获取源代码。但对我来说,它太复杂了(因为我对 WPF 还不熟悉)。在这里,我将利用 WPF Grid
和 GridSplitter
来创建自定义控件,其代码非常简单,反正它能满足我的需求。
Using the Code
SplitContainer 类来自自定义控件
它非常简单,该类继承自 Control
,并且将具有四个 DependencyProperties
Child1Property
–Panel1
用于容纳控件,注册为UIPropertyMetadata
Child2Property
–Panel2
用于容纳控件,注册为UIPropertyMetadata
SplitterDistanceProperty
– 用于设置分割器的距离,double 值OrientationProperty
– 用于设置分割器的方向,枚举值。
以下是代码片段
namespace SplitContainer
{
/// <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:SplitContainer"
///
///
/// 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:SplitContainer;assembly=SplitContainer"
///
/// 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"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public enum Orientation
{
Vertical = 0,
Horizontal
};
public class SplitContainer : Control
{
public static readonly DependencyProperty Child1Property;
/// <summary>Identifies the <see cref="Child2"/> dependency property.</summary>
public static readonly DependencyProperty Child2Property;
public static readonly DependencyProperty SplitterDistanceProperty;
public static readonly DependencyProperty OrientationProperty;
static SplitContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitContainer),
new FrameworkPropertyMetadata(typeof(SplitContainer)));
Child1Property = DependencyProperty.Register
("Child1", typeof(UIElement), typeof(SplitContainer),
new UIPropertyMetadata(null));
Child2Property = DependencyProperty.Register(
"Child2", typeof(UIElement), typeof(SplitContainer),
new UIPropertyMetadata(null));
//It also set the default value to 100
SplitterDistanceProperty = DependencyProperty.Register(
"SplitterDistance", typeof(double), typeof(SplitContainer),
new FrameworkPropertyMetadata(100.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
OrientationProperty = DependencyProperty.Register("Orientation",
typeof(Orientation), typeof(SplitContainer),
new UIPropertyMetadata(null));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public UIElement Child1
{
get { return (UIElement)GetValue(Child1Property); }
set { SetValue(Child1Property, value); }
}
/// <summary>Gets or sets the right or bottom child of the
/// <see cref="SplitContainer"/>, depending
/// on <see cref="Orientation"/>. This is a dependency property.</summary>
/// <value>If <see cref="Orientation"/>
/// is <see cref="System.Windows.Controls.Orientation.Vertical"/>,
/// the bottom child of the <see cref="SplitContainer"/>.
/// If <see cref="Orientation"/> is
/// <see cref="System.Windows.Controls.Orientation.Horizontal"/>,
/// the right child of the <see cref="SplitContainer"/>.</value>
public UIElement Child2
{
get { return (UIElement)GetValue(Child2Property); }
set { SetValue(Child2Property, value); }
}
public double SplitterDistance
{
get { return (double)GetValue(SplitterDistanceProperty); }
set { SetValue(SplitterDistanceProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
}
}
分割器样式
我们为 Vertical
和 Horizontal
设置样式,如下所示,使其看起来更美观
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key ="Color1">#E3EFFF</Color>
<Color x:Key="Color5">#C0DBFF</Color>
<!-- GridSplitter Brushes -->
<LinearGradientBrush x:Key="GridSplitterHorzBackgroundBrush"
EndPoint="0,1" StartPoint="0,0">
<GradientStop Offset="0" Color="{StaticResource Color1}"/>
<GradientStop Offset="1" Color="{StaticResource Color5}"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GridSplitterVertBackgroundBrush"
EndPoint="0,1" StartPoint="1,1">
<GradientStop Offset="0" Color="{StaticResource Color5}"/>
<GradientStop Offset="1" Color="{StaticResource Color1}"/>
</LinearGradientBrush>
<!-- Vertical GridSplitter -->
<Style x:Key="VerticalGridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
<Setter Property="UIElement.Focusable" Value="False"/>
<Setter Property="ShowsPreview" Value="True"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<!--<Setter Property="ResizeBehavior" Value="PreviousAndNext"/>
<Setter Property="ResizeDirection" Value="Columns"/>-->
<Setter Property="Height" Value="Auto"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Background" Value="{DynamicResource GridSplitterVertBackgroundBrush}"/>
</Style>
<!-- Horizontal GridSplitter -->
<Style x:Key="HorizontalGridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
<Setter Property="UIElement.Focusable" Value="False"/>
<Setter Property="ShowsPreview" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<!--<Setter Property="ResizeBehavior" Value="PreviousAndNext"/>
<Setter Property="ResizeDirection" Value="Rows"/>-->
<Setter Property="Height" Value="5"/>
<Setter Property="Width" Value="Auto"/>
<Setter Property="Background" Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
</Style>
</ResourceDictionary>
SplitContainer 的 XAML
以下是在 XAML 中使用 Grid
和 GridSplitter
构建 splitcontainer 的方法
<Style TargetType="{x:Type local:SplitContainer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitContainer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="Column_1"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=SplitterDistance}"/>
<ColumnDefinition Name="Column_2" Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Name="Row_1" Height="*"/>
<RowDefinition Name="Row_2" Height="0"/>
</Grid.RowDefinitions>
<!--Define border-->
<Rectangle Name="Border_Outer" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
Grid.ColumnSpan="2" Stroke="{DynamicResource GridSplitterVertBackgroundBrush}"
Fill="{DynamicResource GridSplitterVertBackgroundBrush}"
StrokeThickness="1" Margin="0,0,0,0"/>
<Rectangle Name="Border_Inner" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
Grid.ColumnSpan="2" Stroke="Black" Fill="White" StrokeThickness="1" Margin="0,1,0,1"/>
<!--Define the Child1 and Child2-->
<ListView Name="child_1" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
BorderBrush="{DynamicResource GridSplitterVertBackgroundBrush}"
BorderThickness="0.5,0.5,0.5,0.5" Margin="1,1,5,1" />
<ListView Name="child_2" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2"
BorderBrush="Black" BorderThickness="0.5,0.5,0.5,0.5" Margin="0.,1,1,1" />
<!--Define Spliter-->
<GridSplitter Name="gridSpliter" Grid.Column="0" Grid.Row="0"
Grid.RowSpan="2" Style="{StaticResource VerticalGridSplitterStyle}"/>
<ContentPresenter Name="CP_1" Margin="0,1,5,1" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child1"/>
<ContentPresenter Name="CP_2" Margin="0,1,0,1" Grid.Column="1"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child2"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="local:SplitContainer.Orientation" Value="Horizontal">
<Setter TargetName="Column_1" Property="Width" Value="*"/>
<Setter TargetName="Column_2" Property="MaxWidth" Value="0"/>
<Setter TargetName="Row_1" Property="Height"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=SplitterDistance}"/>
<Setter TargetName="Row_2" Property="Height" Value="*"/>
<Setter TargetName="Border_Outer" Property="Stroke"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Outer" Property="Fill"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Inner" Property="Stroke"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Inner" Property="Margin" Value="1,0,1,0"/>
<Setter TargetName="child_1" Property="Margin" Value="1,1,1.0,5"/>
<Setter TargetName="child_1" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="child_1" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="child_1" Property="BorderBrush"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="child_2" Property="Margin" Value="1,0,1,0"/>
<Setter TargetName="child_2" Property="Grid.Row" Value="1"/>
<Setter TargetName="child_2" Property="Grid.Column" Value="0"/>
<Setter TargetName="child_2" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="child_2" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="gridSpliter" Property="Style"
Value="{StaticResource HorizontalGridSplitterStyle}"/>
<Setter TargetName="gridSpliter" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="gridSpliter" Property="Grid.Row" Value="0"/>
<Setter TargetName="gridSpliter" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="gridSpliter" Property="VerticalAlignment" Value="Bottom"/>
<Setter TargetName="CP_1" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="CP_1" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="CP_1" Property="Margin" Value="1,1,1.5,5"/>
<Setter TargetName="CP_2" Property="Margin" Value="0,1,1,1"/>
<Setter TargetName="CP_2" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="CP_2" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="CP_2" Property="Grid.Row" Value="1"/>
<Setter TargetName="CP_2" Property="Grid.Column" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我们使用属性触发器来更改 splitContainer
的方向。我们还在每个子控件内部绘制一些矩形,使其看起来像 WindowsForms SplitContainer
。这两行代码非常重要
<ContentPresenter Name="CP_1" Margin="0,1,5,1" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child1"/>
<ContentPresenter Name="CP_2" Margin="0,1,0,1" Grid.Column="1"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child2"/>
它们将 Child1
和 Child2
暴露给应用程序,并使其容纳控件。
在应用程序中使用 SplitContainer
使用起来非常简单,你创建应用程序,在 Window 下,只需将 SplitContainer
放在它下面,就像演示项目中的那样
<Grid>
<SC:SplitContainer Orientation="Horizontal" SplitterDistance="200">
<SC:SplitContainer.Child1>
<SC:SplitContainer Orientation="Vertical">
</SC:SplitContainer>
</SC:SplitContainer.Child1>
<SC:SplitContainer.Child2>
<StackPanel Orientation="Horizontal">
<Button Content="Button on Second Panel" Margin="0" Width="276" />
</StackPanel>
</SC:SplitContainer.Child2>
</SC:SplitContainer>
</Grid>
为了让默认分割器显示颜色,你需要将以下行放在你的 App.XAML 中,如下所示:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/
SplitContainer;component/Themes/Splitter.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
就这样了。