这一章介绍Control 控件。
本章共计51个示例,全都在VS2008下.NET3.5测试通过,点击这里下载: Controls.rar
关于在VS2008新建WPF类库时,有Custom Control和User Control两个选择。
User Control类库会在工程中建立一个XAML文件及其绑定后台代码,前者以<UserControl开头,后者是一个派生自UserControl的类。
Custom Control类库则在工程中创建派生自Control基类的CustomControl1控件,并在构造函数中为CustomControl1指定依赖属性DefaultStyleKeyProperty:
public class CustomControl1 : Control

{
static CustomControl1()

{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
此外,Custom Control类库还会在Theme目录下生成Generic.xaml,这是一个以<ResourceDictionary开头的文件,里面存放着CustomControl1的控件模板。
1.AccessText
这个例子介绍了AccessText元素的使用,以及如何指定一个访问键。
AccessText标记可以使用在任何控件中,如Button、Label等,通过在字母前加一个下划线指定该字母为访问键。连着两个下划线,可以显示一个下划线。
还有就是本例是以StackPanel开始的,并不是一个传统的Page或Window——所有的布局控件都可以这么使用。相应的后台代码文件要继承StackPanel。VS2008目前还不支持创建这样的文件,只有手动设置。例如一个VS2008创建的一个名为Pane1的Page,先修改页面标记Page为StackPanel,这时,后台的Page1.g.i.cs文件会自动改为Pane1:StackPanel,然后再修改Pane1.xaml.cs文件,使Pane1继承于StackPanel。这样就建立了一个所谓的StackPanel控件。
小技巧,在VS2008中,以StackPanel开始的页面的Design视图是没有滚动条的,这就只能看到左上部分——如果xaml内容很多的话,我们可以使用Design视图右上角的“比例尺”,缩小比例可以看到全部试图。
2.BtnColor
按钮中可以放入任何控件,如image。
注意到OnClick4方法。由于设置了背景色,所以按钮点击后的效果看上去像是一个颜色渐变的动画。这是由WPF本身提供的,即使是不设置背景色,点击后也会有颜色渐变效果,只是不太明显而已。
最后一个按钮的点击事件,创建了一个新的按钮加入到XAML控件树中,这与传统的WinForm模型是一致的。
仍然是最后一个按钮,按钮并没有Name值——这是允许的,也会在编译时被序列化,只是这个按钮的Name值为空,而其它属性都还是存在的。
3.BtnStyles
这个示例使用Application级别的资源为Button设定样式。
注意Button标签中Style="{StaticResource SystemResStyle}的使用,以及<Application.Resources>中样式资源,有3种:
一种是直接的键值对,是静态的:
<Setter Property = "Foreground" Value= "DarkBlue"/>
一种是使用WPF内嵌的资源,是动态的:
<Setter Property = "FontWeight" Value= "{DynamicResource {x:Static SystemFonts.MessageFontWeightKey}}"/>
还有一种是触发器,当条件满足时,属性会相应改变:
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Setter Property = "Background" Value="Red"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="true">
<Setter Property = "Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
4.BulletPanel
Bullet用于绘制一组对象的列表。原有的BulletPanel标签已经被BulletDecorator取代。
Bullet中可以放置任何UI元素,如CheckBox、TextBox、RadioButton、Image、TextBlock以及任何Shape。下面是一个标准的用法:
<BulletDecorator >
<BulletDecorator.Bullet>
<Image Source="images"apple.jpg"/>
</BulletDecorator.Bullet>
<TextBlock Name="FontSizeExample">XXX</TextBlock>
</BulletDecorator>
这里把Image作为Bullet,而把TextBlock作为BulletDecorator的子一级元素。Image和TextBlock是可以互换的,没有区别,但是标签<BulletDecorator.Bullet>中只能有一个UI元素。
WindowLoaded负责创建一个BulletDecorator控件,也就是图中左下角的那个。其中创建一个BitmapImage的代码如下:
BitmapImage myBitmapImage = new BitmapImage();
myBitmapImage.BeginInit();
myBitmapImage.UriSource = new Uri(@"pack://application:,,/images/apple.jpg");
myBitmapImage.EndInit();
——这是值得我们学习的。
*补注:面板本身没有文本内容,它们是其他元素的矩形容器。总共有七种面板,每种的布局都不同:BulletPanel、Canvas、DockPanel、Grid、StackPanel、TabPanel和ToolBarOverflowPanel。面板可以层层嵌套。
5.CheckBoxElement
WPF对多选框控件也提供了同样的支持,只是更加简单,每个条目都是一个CheckBox,有自己的Name,以及可以内嵌其它控件。
CheckBox的Checked和Unchecked事件,分别当该条目被选中和被取消选中时触发。
6.CheckBoxStyles
这个示例使用Application级别的资源为CheckBox设定样式。
具体参见示例4 BtnStyles
7.ComboBoxItems
这个示例分为两部分。
首先是ComboBoxItems的显示,以下是部分截取的代码:
<ComboBox Name="cb"….
IsEditable="true" Text="Open Combo Box" IsReadOnly="true"
StaysOpenOnEdit="true" IsDropDownOpen="true">
<ComboBoxItem>Spain - Item 0</ComboBoxItem>
</ComboBox>
它的Text属性是默认的显示值,只在初始的时候作为默认选中项显示,当选中列表中一项时,这和个Text属性值就不再存在了。
IsEditable和IsReadOnly属性共同决定了选项是否可以更改,但改动过的值不会影响该项原先的值——已经存储在后台,必须写代码进行手动修改。
StaysOpenOnEdit属性决定了在编辑时——也就是光标定位在ComboBox的文本框时,下拉框是否仍然显示。
IsDropDownOpen属性决定了默认下拉框是否为打开的,比如说窗体第一次显示时。
其次,就是那个Menu控件的作用了——通过MenuItem的Click事件,根据MenuItem的Header值,也就是ComboBox中相应的索引值,来决定显示哪一个ComboBoxItem。
ComboBoxItem cbi = (ComboBoxItem)
(cb.ItemContainerGenerator.ContainerFromIndex(index));
这条语句重新得到了该索引的ComboBoxItem。
8.ComboBoxSimple
这个例子和示例-8是一样的,从略。
9.ComboBoxStyles
这个例子演示了4个不同样式的ComboBox。这些样式来自Apllication级别。
第一个是SimplePlus样式,设定了默认打开下拉列表(IsDropDownOpen=true)和下拉列表的高度(MaxDropDownHeight=30)。
第二个是Simple样式,设定了前景色和背景色。
第三个仍然使用Simple样式,但给其内部的ComboBoxItem使用SimpleComboBoxItem样式,这就覆盖了之前设置的样式。
最后一个使用了触发器。这使得当鼠标移动到ComboBoxItem时,相应的选项会发生变化。
10.ContentControl
ContentControl表示包含单项内容的控件,这是一个基类,很多控件都派生于此,如Frame、ListBoxItem、Window等等。我们也可以直接使用它。
ContentControl具有有限的默认样式,为此我们要额外设计样式以及控件模板:
<Style x:Key="ContentCtrl" TargetType="{x:Type ContentControl}">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<ContentControl ContentControl.Content="Hello"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
这就添加了前景色、字体大小和宽度、以及一个指定了显示内容的控件模板,那么第一个ContentControl就使用这套样式:
<ContentControl Style="{StaticResource ContentCtrl}"/>
或者创建一个新的数据模板,
<DataTemplate x:Key="template1">
<TextBlock Text="{Binding}" FontSize="12" FontWeight="Bold" TextWrapping="Wrap"></TextBlock>
</DataTemplate>
第二个ContentControl使用了这个模板:
<ContentControl Name="contCtrl" ContentTemplate="{StaticResource template1}"
注:DataTemplate不仅仅用于数据绑定
此外,ContentControl有一个HasContent属性,用以判断控件的Content是否有值。
11.ContextMenu
这个例子演示了ContextMenu的种种用法。
我们可以把ContextMenu捆绑到Button上:
<Button Name="cmButton" Height="30">Button with Context Menu
<Button.ContextMenu>
<ContextMenu Name="cm" Opened="OnOpened" Closed="OnClosed" StaysOpen="true">
<MenuItem Header="File"/>
<MenuItem Header="Recent Files">
<MenuItem Header="ReadMe.txt"/>
</MenuItem>
……
ContextMenu上可以设置OnOpened事件和OnClosed事件,分别发生在上下文菜单弹出和关闭时。StaysOpen属性为false,表示上下文菜只能弹出一次,当鼠标再次右击,不会再弹出;否则,不加限制。
还可以为MenuItem添加tooltip:
<MenuItem Header="Context Menu item with ToolTip">
<MenuItem.ToolTip>
<ToolTip>
Some information.
</ToolTip>
</MenuItem.ToolTip>
</MenuItem>
或者在MenuItem中加入图片:
<MenuItem>
<MenuItem.Header>
<Image Source="data/cat.png"/>
</MenuItem.Header>
</MenuItem>
也可以为一个disabled的控件添加ContextMenu,比如说Button,只要设定它的属性:
ContextMenuService.ShowOnDisabled="True"
最后,就是在代码中动态添加一个ContextMenu,见OnClick方法。
12.ContextMenuShared
This example describes how to create a ContextMenu that can be associated with more than one control. For example the ContextMenu is associated with both a Button and a CheckBox.
这个例子演示了两个Button和两个CheckBox共享页面资源中同样的ContextMenu。
页面资源如下:
<ContextMenu x:Key="MyContextMenu" x:Shared="true">
<MenuItem Header="This MenuItem is checkable" IsCheckable="true" />
<Separator/>
<MenuItem Header="This is a regular MenuItem" />
</ContextMenu>
这里的关键是x:Shared,当设置为 false时,会修改 WPF资源检索行为,以便资源请求会为每个请求创建一个新实例,而不是所有请求共享同一个实例。
指定x:Shared="true"并不常见,因为这已经是默认行为,意味着任何给定资源请求都始终返回同一个实例。对于x:Shared,没有直接的代码等效项。
x:Shared只有在满足以下条件的情况下才是合法的:
·包含具有x:Shared的项的ResourceDictionary必须已编译。ResourceDictionary不能在松散XAML内或用于主题。(所以本例要指定x:Class)
·包含项的ResourceDictionary不得嵌套在另一个 ResourceDictionary内。例如,不能对已经是ResourceDictionary项的Style内部的ResourceDictionary中的项使用 x:Shared。
四个控件使用方法都是一样的,以其中一个为例:
<Button Background="LightBlue"
Content="This Button has a ContextMenu"
ContextMenu="{DynamicResource MyContextMenu}" />
注意:MenuItem的IsCheckable属性,表示菜单项是否可以被挑勾以表示选中。
13.ContextMenuStyles
本例与示例-8的大同小异,不再敷述。
14.ControlProps
本例依次展示了如何在代码中读写按钮的10个属性。分别是:Background、Foreground、FontFamily、FontSize、FontStyle、FontWeight、BorderBrush、HorizontalContentAlignment、VerticalContentAlignment,最后,还有Content。
15.ControlsAll
本例通过按钮的ContextMenu菜单,依次从代码中创建了14个基本控件,分别是
Button、CheckBox、ComboBox、ContextMenu、ListBox、Menu、RadioButton、RepeatButton、ScrollBar、Slider、TextBox、Thumb、ToolBar、ToolTip。
以上各种控件的用法散见于本章各示例中。
16.ControlTemplateExamples
这个例子演示了如何为控件创建新的样式,这里使用了大量的控件模板。
1)
最后,介绍一下ResourceDictionary技术。
74.UserControlNumericUpDown
这个示例演示了如何创建一个WPF用户控件。
为此要建立一个派生于UserControl的XAML,在其中添加若干元素。这里设计了依赖属性ValueProperty,将其绑定到要调整的数值,并为其添加了OnValueChanged事件,触发于这个值改动的时候,会导致TextBlock中的值相应有所变动。
我们使用到了WPF事件机制的后台编程方式,这是对CLR 基本事件模型的更高级封装。其中涉及到了依赖属性。
而在页面中,可以很简单的使用这个用户控件:
<Window… xmlns:uc="clr-namespace:UserControlNumericUpDown">
<Grid>
<uc:NumericUpDown />
</Grid>
</Window>
17.CustomControlNumericUpDown
18.CustomControlNumericUpDownExternalLibrary
19.CustomControlNumericUpDownOneProject
调试不出来
20.Expander
Expander表示一个可显示标题的控件,该标题具有一个用于显示内容的可折叠窗口。对折叠的控制表现为IsExpanded属性,可以把Expander中的所有内嵌元素都隐藏起来。
此外,它的ExpandDirection属性的枚举值,Down、Up、Left和Right:
myExpander.ExpandDirection = ExpandDirection.Down;
ExpandDirection属性决定了内容窗口的打开方向,以下是四个枚举分别对应的效果:
注:我们还可以在折叠与展开的时候,使用事件Collapsed和Expanded,自定义一些小逻辑。
21.ExpanderRichContent
Expander的内容属性为Content,标题属性为Header,都可以嵌入控件元素,如这个示例,Header中放置了一个BulletDecorator:
<Expander.Header>
<BulletDecorator>
<BulletDecorator.Bullet>
<Image Width="10" Source="images"icon.jpg"/>
</BulletDecorator.Bullet>
<TextBlock Margin="20,0,0,0">My Expander</TextBlock>
</BulletDecorator>
</Expander.Header>
而Content中放置了一个ScrollViewer:
<Expander.Content>
<ScrollViewer Height="50">
注:如果展开窗口的内容对于窗口而言太大,您可以在 ScrollViewer 控件中换行显示 Expander 的内容,以便提供可滚动的内容。Expander 控件未自动提供滚动功能。
若要使 Expander 能够正确工作,当 ExpandDirection 属性设置为 Down 或 Up 时,请不要在 Expander 控件上指定 Height。同样,当 ExpandDirection 属性设置为 Left 或 Right 时,不要在 Expander 控件上指定 Width。如果在 Expander 控件显示展开内容的方向上设置大小,将显示由大小参数定义的区域,并在其周围显示一个边框。即使在窗口折叠时,仍会显示该区域。若要设置展开窗口的大小,请在 Expander 控件的内容上或在封闭内容的 ScrollViewer 上设置大小维度。
如果 Expander 控件是 DockPanel 中的最后一个元素,Expander 的大小将设置为填满 DockPanel 的其余区域。若要防止此行为,请将 DockPanel 的 LastChildFill 属性设置为 false,或者确保 Expander 不是 DockPanel 中的最后一个元素。
22.FrameExample
Frame这个控件,多是用来承载导航的XAML和HTML页面的。
<Frame Name = "myFrame" Grid.Column="0" Grid.Row="1" Background="LightBlue"/>
以下是导航语句:
myFrame.Navigate(new System.Uri("http://msdn.microsoft.com/vcsharp/"));
myFrame.Navigate(new System.Uri("AnotherAvalonPage.xaml",
UriKind.RelativeOrAbsolute));
23.GridSplitterProperties
这个示例演示了GridSplitter控件的使用。
GridSplitter派生于Thumb,只能使用于Grid中,而且要指定它所在的row和column位置(以及行和列的span):
GridSplitter split = new GridSplitter();
split.Width = 6;
grid.Children.Add(split);
Grid.SetColumn(split, 2);
Grid.SetRow(split, 1);
GridSplitter可以和其它元素共享相同的单元格,这就存在谁遮挡了谁的问题——后出现的元素总是在前面,为此,需要设置margin,以避免重叠:
split.Margin = new Thickness(10);
一般会让GridSplitter跨越整行或整列,比较合理:
Grid.SetRowSpan(split, 3);
Grid.SetColumnSpan(split, 3);
一般把GridSplitter单独放在一个或一组单元格中
在这个例子中,通过设置GridSplitter的左对齐,可以看到遮挡效果,因为在Grid(1,1)这个位置,有两个元素,GridSplitter是后出现的元素:
<StackPanel Grid.Row="1" Grid.Column="1" Background="Red">
<TextBlock>Row 1 Col 1</TextBlock>
</StackPanel>
<GridSplitter Name="myGridSplitter" Grid.Column="1" Grid.Row="1" Width="5"/>
下面讨论GridSplitter的几个属性:
1)ShowsPreview属性,默认为fasle,也就是说拖动GridSplitter时看不到效果。而设置为true,则要等到松开鼠标,鸽子才会改变尺寸。
2)DragIncrement属性,设置这个值,可以决定每次鼠标拖动的距离。默认值为1。
3)KeyboardIncrement属性,设置每按下一次箭头键时移动 GridSplitter 控件的距离。默认值为1。
VerticalAlignment和HorizontalAlignment,是用来控制splitter是水平的还是垂直的。默认情况下,HorizontalAlignment为Right且VerticalAlignment为Stretch,此时splitter位于格子的右边,移动splitter会左右移动它所在列的宽度分配。我们可以改变HorizontalAlignment为Left或Center,而保持VerticalAlignment为Stretch不变。
我们可以通过splitter的ResizeBehavior枚举属性,改变上述行为,让我们选择哪个列会被splitter影响:
CurrentAndNext 等效于splitter靠右
PreviousAndCurrent等效于splitter靠左
PreviousAndNext 等效于splitter居中
BasedOnAlignment 等效于由splitter的HorizontalAlignment和VerticalAlignment来决定
相应的,设置HorizontalAlignment为Stretch而VerticalAlignment为Top、Bottom或Center,从而使splitter为水平的。
我们可以利用ResizeDirection属性,改变splitter影响的方向,有三种枚举值:Auto、Columns、Rows。对于水平splitter,按理说应该是上下移动,但是如果把ResizeDirection属性设为Columns,
则可以左右移动splitter,但是splitter的效果就看不到了。
24.GridSplitterRowColumn
这个示例演示了让GridSplitter跨越行或列,这是一种比较合理的方式,从外观上:
<GridSplitter Grid.Row="1" Grid.ColumnSpan="3"
25.GroupBox
GroupBox就是一个很简单的容器,用来盛放多个控件在其中,这样可以同时控制它们的共同属性,如GroupBox不可见时,则其中所有控件也都不再可见。
26.HeaderedContentControl
HeaderedContentControl为包含单项内容并具有标头的所有控件提供基实现,有两个关键属性,Content和Header属性,可设置为任何类型的对象。
HeaderedContentControl具有有限的默认样式,其外观非常简单。如果希望增强控件的外观,可以创建新的ControlTemplate,如下:
<Style x:Key="{x:Type HeaderedContentControl}" TargetType="{x:Type HeaderedContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<StackPanel>
<ContentPresenter ContentSource="Header"/>
<ContentPresenter ContentSource="Content"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
这就重写了所有的HeaderedContentControl控件,使用了ContentPresenter并且规定,Header在上,Content在下。
在本例中,我们可以使用ContentControl来代替表示Content属性,也就是说,以下两种声明方式是相同的:
//声明方式1
<HeaderedContentControl HeaderTemplate="{StaticResource template2}" Header="Header"
ContentTemplate="{StaticResource template3}" Content=" This is the content."/>
//声明方式2
<HeaderedContentControl HeaderTemplate="{StaticResource template2}" Header="Header">
<ContentControl ContentTemplate="{StaticResource template3}"
Content="This is the content" />
</HeaderedContentControl>
这里,我们还可以细化到为Header和Content指定DataTemplate:
<DataTemplate x:Key="template2">
<TextBlock Text="{Binding}" Foreground="Green" FontSize="16"…></TextBlock>
</DataTemplate>
<DataTemplate x:Key="template3">
<TextBlock Text="{Binding}" Foreground="Brown" FontSize="12"…></TextBlock>
</DataTemplate>
注:有关数据模板的技术介绍参见“数据绑定”一章
注:Expander、GroupBox、TabItem派生于HeaderedContentControl。
再有就是,HeaderedContentControl有一个HasHeader只读属性,用来判断Header是否有值。
27.HeaderedItemsControl
HeaderedItemsControl表示包含多个项并且具有标题的控件。类似于HeaderedContentControl,它的可视化样式也不多,需要手动创建数据模板和控件模板:
<Style x: