欢迎大家提出意见,一起讨论!
转载请标明是引用于 http://blog.csdn.net/chenyujing1234
源码下载: http://www.rayfile.com/zh-cn/files/459fa05c-bf69-11e1-b0ff-0015c55db73d/
编译平台:VS2008 + .Net Framework 3.5
语言: C#
此博客的目的:
以浏览器程序的实现,学习自定义TabControl的方法,使之能实现系统Tab不具有的功能:
(1)排列方式扩展为:Top、Bottom、Left、Right
(2)增加对TabItem的事件处理
(3)
一、浏览器实现
效果图:
1、启动窗口显示两个按钮
它们的处理函数对应是去创建窗口WindowUsingItemProperty或WindowUsingItemsSourceProperty
private void Items_Click(object sender, RoutedEventArgs e)
{
// WindowUsingItemProperty对应于WindowUsingItemsProperty.xaml文件中的
// <Window x:Class="Test.WindowUsingItemProperty"
var win = new WindowUsingItemProperty();
win.Show();
}
private void ItemsSource_Click(object sender, RoutedEventArgs e)
{
var win = new WindowUsingItemsSourceProperty();
win.Show();
}
接下来的设计我们以WindowUsingItemProperty为例来说明.
2、加入自定义控件Wpf.TabControl
在WindowUsingItemProperty窗口的界面设计文件WindowUsingItemProperty.xaml中加入自定义控件Wpf.TabControl.
3、浏览器界面控件布局
首先将界面分为三行:
3、1 每一行加入TextBlock与TextBox,用于输入网址
当输入网址确认后的处理是获得浏览器对象并让浏览器导航到指定的网址
private void textBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// 按了回车键
if (e.Key == Key.Return)
{
try
{
// 鼠标变成等待
Cursor = System.Windows.Input.Cursors.Wait;
// 通过自己封闭的函数GetCurrentWebBrowser 获得当前的浏览器
System.Windows.Forms.WebBrowser browser = GetCurrentWebBrowser();
if (browser == null) return;
// 浏览器导航到指定的网址
browser.Navigate(textBox.Text);
}
finally
{
Cursor = System.Windows.Input.Cursors.Arrow;
}
}
}
而浏览器的获得是通过当前TabItem来得到的
private System.Windows.Forms.WebBrowser GetCurrentWebBrowser()
{
// 获得TabControl当前选择的TabItem
Wpf.Controls.TabItem item = tabControl.SelectedItem as Wpf.Controls.TabItem;
if (item == null) return null;
// 获得选中的TabItem中的Content,并转化为WindowFormsHost
WindowsFormsHost host = item.Content as WindowsFormsHost;
if (host == null) return null;
// 获得WindowFormsHost的Child,来得到浏览器对象
System.Windows.Forms.WebBrowser browser = host.Child as System.Windows.Forms.WebBrowser;
return browser;
}
3、2 根据自定义控件TabControl的规则加入节点
请注意空间 r: 的原因。
3、2、1 对TabControl中的TabItem显示的三种状态进行设置。
目的是为了看起来像IE7的样子。
3、2、2 加入TabItem
TabItem中包括了对Header、Icon的设置,及在Item中的内容中包裹WebI浏览器
在 3、1 中我们看到GetCurrentWebBrowser的获得有两个步骤:
(1)获得选中的TabItem中的Content,并转化为WindowFormsHost
(2)获得WindowFormsHost的Child,来得到浏览器对象
// 获得选中的TabItem中的Content,并转化为WindowFormsHost
WindowsFormsHost host = item.Content as WindowsFormsHost;
if (host == null) return null;
// 获得WindowFormsHost的Child,来得到浏览器对象
System.Windows.Forms.WebBrowser browser = host.Child as System.Windows.Forms.WebBrowser;
3、2、2、1 浏览器DocumentTitleChanged、Navigated两个事件的处理
Browser_DocumentTitleChanged主要是更新TabItems的头特性、增加一个Icon到tabItem、把浏览器的DocumentTitle加入到tabItem中的Head中
void Browser_DocumentTitleChanged(object sender, EventArgs e)
{
System.Windows.Forms.WebBrowser browser = sender as System.Windows.Forms.WebBrowser;
if (browser == null) return;
// 更新TabItems的头特性
Wpf.Controls.TabItem item = tabControl.SelectedItem as Wpf.Controls.TabItem;
// 增加一个Icon到tabItem
BitmapImage image = new BitmapImage(new Uri("pack://application:,,,/Test;component/Images/ie.ico"));
Image img = new Image();
img.Source = image;
img.Width = 16;
img.Height = 16;
img.Margin = new Thickness(2, 0, 2, 0);
if (item != null) item.Icon = img;
// 把浏览器的DocumentTitle加入到tabItem中的Head中
TextBlock tb = new TextBlock();
tb.Text= browser.DocumentTitle;
tb.TextTrimming = TextTrimming.CharacterEllipsis;
tb.TextWrapping = TextWrapping.NoWrap;
if (item != null) item.Header = tb;
}
Browser_Navigated主要是将浏览器当前的网址传给textBox
void Browser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
// 获得Web浏览器
System.Windows.Forms.WebBrowser browser = sender as System.Windows.Forms.WebBrowser;
if (browser == null) return;
// 将浏览器当前的网址传给textBox
textBox.Text = browser.Url.ToString();
}
二、自定义控件Wpf.TabControl
1、控件外观设计(在Generic.xaml文件中)
1、1 整个文件以关键字ResourceDictionary开始
它定义了此控件的空间名字。
1、2 TabControl 样式, 定义 TabControl 外表的样式
在Test工程中若用到TabControl控件,那么得采用 clr-namespace:Wpf.Controls::TabControl的形式;
引用到TabControl 控件中的属性时,得采用类似clr-namespace:Wpf.Controls::TabControl.TabItemNormalBackground的形式。
1、2、1 控件Template 的设计
如上图所示,控件的Template属性默认为TabControlTabPlacementTop.
它被分为两行五列,总共有六种样式(Top、Bottom、Left、Right)。以TabControlTabPlacementTop为例,如下:
TabControlTabPlacementTop里有六样元素:
ToggleButton:显示一个context 菜单,这个菜单显示所有的TabItem;
新建TabItem的按钮:
左RepeatButton:用来在view中向左滚动TabItems
ScrollViewer: 存放TabItem用的滚动面板
右RepeatButton:用来在view中向右滚动TabItems
内容面板:
外表看来是这样:
XAML中代码如下:
<!-- Toggle 按钮显示一个context 菜单,这个菜单显示所有的TabItem-->
<ToggleButton
x:Name="PART_DropDown"
Height="22"
Width="16"
VerticalAlignment="Bottom"
Margin="0,0,0,-1"
Grid.Column="0"
KeyboardNavigation.TabIndex="1"
Style="{StaticResource DropDownToggleButtonStyle}"
Visibility="{Binding IsUsingItemsSource, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource _inverseBooleanConverter}}"
ToolTip="Display all the tabs in a menu"/>
<!-- 新建TabItem的按钮 -->
<Button
x:Name="PART_NewTabButton"
Grid.Column="1"
Height="22"
VerticalAlignment="Bottom"
Margin="-1,5,0,-1"
KeyboardNavigation.TabIndex="2"
Style="{StaticResource NewTabButtonStyle}"
Width="24"
Visibility="{Binding Path=AllowAddNew, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource boolConverter}}"
ToolTip="Add a New tab">
<Image Source="pack://application:,,,/Wpf.TabControl;component/Images/newtab.ico"/>
</Button>
<!-- Repeate 按钮用来在view中滚动TabItems -->
<RepeatButton
x:Name="PART_RepeatLeft"
Panel.ZIndex="1"
Grid.Column="2"
Height="22"
KeyboardNavigation.TabIndex="3"
VerticalAlignment="Bottom"
Style="{StaticResource RepeatButtonScrollLeftOrUpStyle}"
Margin="-1,5,0,-1"
ToolTip="Scroll the Tab Items to the Left"
Visibility="{Binding ElementName=TabPanel, Path=CanScrollLeftOrUp, Converter={StaticResource boolConverter}}"/>
<ScrollViewer x:Name="PART_ScrollViewer" Grid.Column="3" Panel.ZIndex="1" CanContentScroll="True" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Hidden">
<l:TabPanel x:Name="TabPanel" IsItemsHost="True" SnapsToDevicePixels="True" />
</ScrollViewer>
<RepeatButton
x:Name="PART_RepeatRight"
Panel.ZIndex="1"
Grid.Column="4"
Height="22"
VerticalAlignment="Bottom"
Style="{StaticResource RepeatButtonScrollRightOrDownStyle}"
Margin="-1,5,0,-1"
ToolTip="Scroll the Tab Items to the Right"
Visibility="{Binding ElementName=TabPanel, Path=CanScrollRightOrDown, Converter={StaticResource boolConverter}}" />
<!-- 内容面板-->
<Border x:Name="ContentPanel"
Grid.ColumnSpan="5" Grid.Row="1"
Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="{Binding Path=BorderThickness, RelativeSource={RelativeSource TemplatedParent}}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="5"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost"
Content="{Binding Path=SelectedContent, RelativeSource={RelativeSource TemplatedParent}}"
ContentSource="SelectedContent"
ContentTemplate="{Binding Path=ContentTemplate, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{Binding Path=SnapsToDevicePixels, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{Binding Path=Padding, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</Grid>
</ControlTemplate>
<!-- TabStripPlacement属性为Bottom时的控件模板 -->
<ControlTemplate x:Key="TabControlTabPlacementBottom" TargetType="{x:Type l:TabControl}">
<Grid SnapsToDevicePixels="{Binding Path=SnapsToDevicePixels, RelativeSource={RelativeSource TemplatedParent}}" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="*" />
<RowDefinition x:Name="RowDefinition1" Height="Auto" />
</Grid.RowDefinitions>
<!-- Toggle button which displays a context menu with a menu item for each tab which can be used to select a tab that is not in view-->
<ToggleButton
x:Name="PART_DropDown"
Grid.Row="1"
Height="22"
Width="16"
VerticalAlignment="Top"
Margin="0,-1,0,0"
Grid.Column="0"
Style="{StaticResource DropDownToggleButtonStyle}"
Visibility="{Binding IsUsingItemsSource, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource _inverseBooleanConverter}}"
ToolTip="Display all the tabs in a menu"/>
<Button
x:Name="PART_NewTabButton"
Grid.Row="1"
Grid.Column="1"
Height="22"
VerticalAlignment="Top"
Margin="-1,-1,0,0"
Style="{StaticResource NewTabButtonStyle}"
Width="24"
Visibility="{Binding Path=AllowAddNew, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource boolConverter}}"
ToolTip="Add a New tab">
<Image Source="pack://application:,,,/Wpf.TabControl;component/Images/newtab.ico"/>
</Button>
<!-- Repeat Buttons used for scrolling the TabItems into view-->
<RepeatButton
x:Name="PART_RepeatLeft"
Panel.ZIndex="1"
Grid.Row="1"
Grid.Column="2"
Height="22"
VerticalAlignment="Top"
Style="{StaticResource RepeatButtonScrollLeftOrUpStyle}"
Margin="-1,-1,0,0"
ToolTip="Scroll the Tab Items to the Left"
Visibility="{Binding ElementName=TabPanel, Path=CanScrollLeftOrUp, Converter={StaticResource boolConverter}}"/>
<ScrollViewer x:Name="PART_ScrollViewer" Grid.Row="1" Grid.Column="3" Panel.ZIndex="1" CanContentScroll="True" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Hidden">
<l:TabPanel x:Name="TabPanel" IsItemsHost="True" SnapsToDevicePixels="True" />
</ScrollViewer>
<RepeatButton
x:Name="PART_RepeatRight"
Panel.ZIndex="1"
Grid.Row="1"
Grid.Column="4"
Height="22"
VerticalAlignment="Top"
Style="{StaticResource RepeatButtonScrollRightOrDownStyle}"
Margin="-1,-1,0,0"
ToolTip="Scroll the Tab Items to the Right"
Visibility="{Binding ElementName=TabPanel, Path=CanScrollRightOrDown, Converter={StaticResource boolConverter}}" />
<!-- Content Panel-->
<Border x:Name="ContentPanel"
Grid.ColumnSpan="5"
Grid.Row="0"
Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="{Binding Path=BorderThickness, RelativeSource={RelativeSource TemplatedParent}}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost"
Content="{Binding Path=SelectedContent, RelativeSource={RelativeSource TemplatedParent}}"
ContentSource="SelectedContent"
ContentTemplate="{Binding Path=ContentTemplate, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{Binding Path=SnapsToDevicePixels, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{Binding Path=Padding, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
1、3 定义TabItem属性、样式
定义属性:
定义TabItem节点的控件模板,里面是一个Border,Border里分三列,分别放Icon、Header 和按钮。
可以看出Icon是在一个ContentPresenter里,通过Content="{TemplateBinding Icon}"绑定节点名字为icon;
Header是在一个Border里,通过Content="{TemplateBinding Header}"绑定节点名字为Header。
2、 TabConrol逻辑及数据设计
2、1 为节点TabControl中的Template属性加载处理函数。
C#提供了绑定的接口,即重载OnApplyTemplate函数。
通过this.Template.FindName找到对应TabControl中的Template里的5个元素。
/// <summary>
/// OnApplyTemplate override
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// 为'PART_DropDown'按钮设计处理函数
_toggleButton = this.Template.FindName("PART_DropDown", this) as ToggleButton;
if (_toggleButton != null)
{
// 创建一个context menu
ContextMenu cm = new ContextMenu { PlacementTarget = _toggleButton, Placement = PlacementMode.Bottom };
// 创建toggleButton的IsChecked属性与Context Menu的IsOpen属性的绑定
Binding b = new Binding
{
Source = _toggleButton,
Mode = BindingMode.TwoWay,
Path = new PropertyPath(ToggleButton.IsCheckedProperty)
};
cm.SetBinding(ContextMenu.IsOpenProperty, b);
_toggleButton.ContextMenu = cm;
_toggleButton.Checked += DropdownButton_Checked;
}
ScrollViewer scrollViewer = this.Template.FindName("PART_ScrollViewer", this) as ScrollViewer;
// set up event handlers for the RepeatButtons Click event
RepeatButton repeatLeft = this.Template.FindName("PART_RepeatLeft", this) as RepeatButton;
if (repeatLeft != null)
{
repeatLeft.Click += delegate
{
if (scrollViewer != null)
scrollViewer.LineLeft();
};
}
RepeatButton repeatRight = this.Template.FindName("PART_RepeatRight", this) as RepeatButton;
if (repeatRight != null)
{
repeatRight.Click += delegate
{
if (scrollViewer != null)
scrollViewer.LineRight();
};
}
// 设置按钮 'New Tab'单击时的事件处理函数
_addNewButton = this.Template.FindName("PART_NewTabButton", this) as ButtonBase;
if (_addNewButton != null)
_addNewButton.Click += ((sender, routedEventArgs) => AddTabItem());
}