引言

Tabcontrol控件也是我们在项目中经常用到的一个控件,用它将相关的信息组织在一起分类显示。

简介

 

 

============================================

自定义TabitemPanel

WpfScrollableTabControl.zip

 

============================================

 

自动选择第一个TabItem

Auto-Select First Item Using XAML for Selector-Derived Controls (ListBox, ListView, TabControl, etc)

<Style x:Key="SelectorAutoSelectStyle"
TargetType="{x:Type Selector}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="SelectedItem"
Value="{x:Null}" />
<Condition Property="HasItems"
Value="True" />
</MultiTrigger.Conditions>
<Setter Property="SelectedIndex"
Value="0" />
</MultiTrigger>
</Style.Triggers>
</Style> <Style BasedOn="{StaticResource SelectorAutoSelectStyle}"
TargetType="{x:Type ListBox}" /> <Style BasedOn="{StaticResource SelectorAutoSelectStyle}"
TargetType="{x:Type ListView}" /> <Style BasedOn="{StaticResource SelectorAutoSelectStyle}"
TargetType="{x:Type TabControl}" />
In WPF, SelectionChanged does not mean that the selection changed

Windows Presentation Foundation's Routed Events can lead to unexpected or at least nonintuitive behavior when using TabControls that contain ListViews and/or ComboBoxes. A routed event generally bubbles from the control that raised it, up the whole element tree until the root. On its way up it invokes handlers on multiple listeners. This makes a lot of sense in theButtonBase.Click Event: if a button is clicked, then its containing element is also clicked.

By design, the Selector.SelectionChanged Event is such a routed event. TabItem, ListBox, and ComboBox all inherit from Selector, so if you put them in a hierarchy they will register on each other's events. A ComboBox that appears via a template in a ListBox will raise the SelectionChanged event of that ListBox - even if the user didn't select a new ListBoxItem. If you put that ListBox in a TabControl, then the SelectionChanged on that TabControl will also be fired - even if the user didn't select a new TabItem.

 

绑定List<T>对象,自动生成TabItems

<controls:TabControlEx   Grid.Row="11" Grid.Column="1"
x:Name="CalculationListBox"
ItemsSource="{Binding CalculationViewModels}">
<controls:TabControlEx.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Background" Value="{Binding CalculationType,Converter={StaticResource CalculateTypesToColorConverter}}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding CurrentCalculation.Name}" HorizontalAlignment="Left" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</controls:TabControlEx.ItemContainerStyle>
</controls:TabControlEx>

 

 

 

避免TabItem不被选中时销毁,重新实现TabControl控件

 

解决方案一:

WPF TabControl: Turning Off Tab Virtualization

 

解决方案二:

WPF's forgetful TabControl

It reference the article: Persist the Visual Tree when switching tabs in the WPF TabControl

but there is a bug,to solve the problem:

Although the demo provided in the article works, I did have to tweak the code to get it to work with my project. I found that my TabControl was being loaded, then unloaded, then loaded again when my application started. This was causing the TabLoaded event handler in PersistTabItemsSourceHandler to fire twice. The second time it fired, AttachCollectionChangedEvent() was throwing a NullReferenceException because Tab had been set to null in PersistTabItemsSourceHandler.Dispose() when the TabControl was unloaded.

 

To fix this I first moved the calls to AttachCollectionChangedEvent() and LoadItemsSource() from the TabLoaded event handler to the constructor. Thus, AttachCollectionChangedEvent() was only called once, as soon as Tab had been set.

 

This got rid of the NullReferenceException, however now adding a new view model did not add a new item to the TabControl. This was because the TabControl was still being unloaded, during which the CollectionChanged event was being detached. The only way I could find to solve this was to remove the call to PersistTabItemsSourceHandler.Dispose() in PersistTabBehaviour.RemoveFromItemSourceHandlers().

 

 

解决方案三:

 

using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives; namespace SharedUtilities.Controls
{
/// <summary>
/// The standard WPF TabControl is quite bad in the fact that it only
/// even contains the current TabItem in the VisualTree, so if you
/// have complex views it takes a while to re-create the view each tab
/// selection change.Which makes the standard TabControl very sticky to
/// work with. This class along with its associated ControlTemplate
/// allow all TabItems to remain in the VisualTree without it being Sticky.
/// It does this by keeping all TabItem content in the VisualTree but
/// hides all inactive TabItem content, and only keeps the active TabItem
/// content shown.
/// </summary>
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof (Panel))]
public class TabControlEx : TabControl
{
#region Data private Panel itemsHolder = null; #endregion #region Ctor public TabControlEx()
: base()
{
// this is necessary so that we get the initial databound selected item
this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
this.Loaded += TabControlEx_Loaded;
} #endregion #region Public/Protected Methods /// <summary>
/// get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
} /// <summary>
/// when the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e); if (itemsHolder == null)
{
return;
} switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
itemsHolder.Children.Clear();
break; case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
ContentPresenter cp = FindChildContentPresenter(item);
if (cp != null)
{
itemsHolder.Children.Remove(cp);
}
}
} // don't do anything with new items because we don't want to
// create visuals that aren't being shown UpdateSelectedItem();
break; case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
} /// <summary>
/// update the visible child in the ItemsHolder
/// </summary>
/// <param name="e"></param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
} /// <summary>
/// copied from TabControl; wish it were protected in that class instead of private
/// </summary>
/// <returns></returns>
protected TabItem GetSelectedTabItem()
{
object selectedItem = base.SelectedItem;
if (selectedItem == null)
{
return null;
}
TabItem item = selectedItem as TabItem;
if (item == null)
{
item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
}
return item;
} #endregion #region Private Methods /// <summary>
/// in some scenarios we need to update when loaded in case the
/// ApplyTemplate happens before the databind.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TabControlEx_Loaded(object sender, RoutedEventArgs e)
{
UpdateSelectedItem();
} /// <summary>
/// if containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
} /// <summary>
/// generate a ContentPresenter for the selected item
/// </summary>
private void UpdateSelectedItem()
{
if (itemsHolder == null)
{
return;
} // generate a ContentPresenter if necessary
TabItem item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
} // show the right child
foreach (ContentPresenter child in itemsHolder.Children)
{
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
} /// <summary>
/// create the child ContentPresenter for the given item (could be data or a TabItem)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
private ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
} ContentPresenter cp = FindChildContentPresenter(item); if (cp != null)
{
return cp;
} // the actual child to be added. cp.Tag is a reference to the TabItem
cp = new ContentPresenter();
cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
itemsHolder.Children.Add(cp);
return cp;
} /// <summary>
/// Find the CP for the given object. data could be a TabItem or a piece of data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
{
data = (data as TabItem).Content;
} if (data == null)
{
return null;
} if (itemsHolder == null)
{
return null;
} foreach (ContentPresenter cp in itemsHolder.Children)
{
if (cp.Content == data)
{
return cp;
}
} return null;
} #endregion
}
}

 

Style自定义

 

自定义ContentTemplate

<Style TargetType="{x:Type controls:TabControlEx}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TabControlEx}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="row0" Height="Auto" />
<RowDefinition x:Name="row1" Height="4" />
<RowDefinition x:Name="row2" Height="*" />
</Grid.RowDefinitions> <TabPanel x:Name="tabpanel"
Background="Transparent"
Margin="0"
Grid.Row="0"
IsItemsHost="True" /> <Grid x:Name="divider"
Grid.Row="1" Background="Black"
HorizontalAlignment="Stretch" /> <ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Grid x:Name="PART_ItemsHolder" />
</ScrollViewer>
</Grid>
<!-- no content presenter -->
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Top">
<Setter TargetName="tabpanel" Property="Grid.Row" Value="0" />
<Setter TargetName="divider" Property="Grid.Row" Value="1" />
<Setter TargetName="PART_ItemsHolder" Property="Grid.Row" Value="2" />
<Setter TargetName="row0" Property="Height" Value="Auto" />
<Setter TargetName="row1" Property="Height" Value="4" />
<Setter TargetName="row2" Property="Height" Value="*" />
</Trigger>
<Trigger Property="TabStripPlacement" Value="Bottom">
<Setter TargetName="tabpanel" Property="Grid.Row" Value="2" />
<Setter TargetName="divider" Property="Grid.Row" Value="1" />
<Setter TargetName="PART_ItemsHolder" Property="Grid.Row" Value="0" />
<Setter TargetName="row0" Property="Height" Value="*" />
<Setter TargetName="row1" Property="Height" Value="4" />
<Setter TargetName="row2" Property="Height" Value="Auto" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

 

<!--The Style for TabItems (strips).-->
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions> <TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
IsItemsHost="true"
Margin="0 3 0 2" /> <Border BorderThickness="1" Grid.Row="1" BorderBrush="Black">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent" />
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

 

为所有TabItem设定统一的默认风格。

How to set the default style for tabitem in a tabcontrol's style

<TabControl ItemContainerStyle="{StaticResource MyTabItem}"/>

 

 

 

 

参考:

want to make scrollable tabs for a tabcontrol

How to change appearance of TabItems in a scrolling WPF TabControl?

WPF: TabControl Series - Part 1: Colors and Sizes

[WPF系列]-基础系列 TabControl应用的更多相关文章

  1. [WPF系列]-基础系列 Property Trigger, DataTrigger & EventTrigger

    So far, we worked with styles by setting a static value for a specific property. However, using trig ...

  2. WPF入门教程系列一——基础

    一. 前言   最近在学习WPF,学习WPF首先上的是微软的MSDN,然后再搜索了一下网络有关WPF的学习资料.为了温故而知新把学习过程记录下来,以备后查.这篇主要讲WPF的开发基础,介绍了如何使用V ...

  3. WPF入门教程系列二——Application介绍

    一.Application介绍 WPF和WinForm 很相似, WPF与WinForm一样有一个 Application对象来进行一些全局的行为和操作,并且每个 Domain (应用程序域)中仅且只 ...

  4. WPF入门教程系列(二) 深入剖析WPF Binding的使用方法

    WPF入门教程系列(二) 深入剖析WPF Binding的使用方法 同一个对象(特指System.Windows.DependencyObject的子类)的同一种属性(特指DependencyProp ...

  5. WPF入门教程系列(一) 创建你的第一个WPF项目

    WPF入门教程系列(一) 创建你的第一个WPF项目 WPF基础知识 快速学习绝不是从零学起的,良好的基础是快速入手的关键,下面先为大家摞列以下自己总结的学习WPF的几点基础知识: 1) C#基础语法知 ...

  6. WPF入门教程系列一

    WPF入门教程 一.  前言  公司项目基于WPF开发,最近项目上线有点空闲时间写一篇基于wpf的基础教材,WPF也是近期才接触,学习WPF也是在网上查资料与微软的MSDN进行学习,写本博客的目为了温 ...

  7. .Net5 WPF快速入门系列教程

    一.概要 在工作中大家会遇到需要学习新的技术或者临时被抽调到新的项目当中进行开发.通常这样的情况比较紧急没有那么多的时间去看书学习.所以这里向wpf技术栈的开发者分享一套wpf教程,基于.net5框架 ...

  8. iOS系列 基础篇 03 探究应用生命周期

    iOS系列 基础篇 03 探究应用生命周期 目录: 1. 非运行状态 - 应用启动场景 2. 点击Home键 - 应用退出场景 3. 挂起重新运行场景 4. 内存清除 - 应用终止场景 5. 结尾 本 ...

  9. iOS系列 基础篇 04 探究视图生命周期

    iOS系列 基础篇 04 探究视图生命周期 视图是应用的一个重要的组成部份,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻. 以视图的四种状态为基础,我们来系统了解一下视 ...

随机推荐

  1. C#根据句柄改变窗体控件值

    需求是这样,有个程序界面我们需要通过自己的程序持续输入数据,界面如图. 可以获得控件的句柄而用钩子写入值.这里需要用到spy++工具.在VS的工具下有个spy++工具,打开如下图 通过这个工具可以获得 ...

  2. DataSet转化为实体集合类

    /// <summary> /// DataSet转换为实体类 /// </summary> /// <typeparam name="T">实 ...

  3. 利用节点更改table内容

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> new document ...

  4. js自动切换图片

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. Tomcat指定的服务已存在

    解压Tomcat8.0后,运行service.bat install,结果安装未成功,进入logs文件夹,查看错误日志,提示: 指定的服务已存在. Failed to install service ...

  6. 数据结构:单链表结构字符串(python版)改进

    此篇文章的replace实现了字符串类的多次匹配,但依然有些不足. 因为python字符串对象为不变对象,所以replace方法并不修改原先的字符串,而是返回修改后的字符串. 而此字符串对象时用单链表 ...

  7. 超简单的激活Microsoft Office 2016 for Mac 方法

    1.简介: 2016年9月14日更新本博客,激活工具同样适用于Office 15.25(160817)版本.我此前在国外网站上找到一个App,下载之后运行,直接点击一个黑色开锁的标识按钮,输入系统密码 ...

  8. JQuery 实现两列等高并自适应高度

    想要使用 JQuery 实现两列等高并自适应高度,其实也很简单,原理就是取得左右两边的高度,然后判断这个值,把大的值赋给小的就行了.看代码: $(document).ready(function() ...

  9. Android中通过线程实现更新ProgressDialog(对话进度条)

    作为开发者我们需要经常站在用户角度考虑问题,比如在应用商城下载软件时,当用户点击下载按钮,则会有下载进度提示页面出现,现在我们通过线程休眠的方式模拟下载进度更新的演示,如图(这里为了截图方便设置对话进 ...

  10. android FrameLayout详解

    首先看演示: FrameLayout框架布局是最简单的布局形式.所有添加到这个布局中的视图都以层叠的方式显示.第一个添加的控件被放在最底层,最后一个添加到框架布局中的视图显示在最顶层,上一层的控件会覆 ...