Shun 2 лет назад
Родитель
Сommit
dad5f925d0

+ 32 - 0
src/YSAI.Controls/tabcontrol/Converters.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace YSAI.Controls.tabcontrol
+{
+    class InverseBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            bool flag = false;
+            if (value is bool)
+            {
+                flag = (bool)value;
+            }
+            else if (value is bool?)
+            {
+                bool? nullable = (bool?)value;
+                flag = nullable.Value;
+            }
+            return (flag ? Visibility.Collapsed : Visibility.Visible);
+
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return ((value is Visibility) && (((Visibility)value) == Visibility.Collapsed));
+
+        }
+    }
+}

+ 85 - 0
src/YSAI.Controls/tabcontrol/Helper.cs

@@ -0,0 +1,85 @@
+using System.IO;
+using System.Windows;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Xml;
+
+namespace YSAI.Controls.tabcontrol
+{
+    class Dimension
+    {
+        public double Height;
+        public double MaxHeight = double.PositiveInfinity;
+        public double MinHeight;
+        public double Width;
+        public double MaxWidth = double.PositiveInfinity;
+        public double MinWidth;
+    }
+
+    class Helper
+    {
+        /// <summary>
+        /// Find a specific parent object type in the visual tree
+        /// </summary>
+        public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
+        {
+            DependencyObject dObj = VisualTreeHelper.GetParent(outerDepObj);
+            if (dObj == null)
+                return null;
+
+            if (dObj is T)
+                return dObj as T;
+
+            while ((dObj = VisualTreeHelper.GetParent(dObj)) != null)
+            {
+                if (dObj is T)
+                    return dObj as T;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Find the Panel for the TabControl
+        /// </summary>
+        public static TabPanel FindVirtualizingTabPanel(Visual visual)
+        {
+            if (visual == null)
+                return null;
+
+            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
+            {
+                Visual child = VisualTreeHelper.GetChild(visual, i) as Visual;
+
+                if (child != null)
+                {
+                    if (child is TabPanel)
+                    {
+                        object temp = child;
+                        return (TabPanel)temp;
+                    }
+
+                    TabPanel panel = FindVirtualizingTabPanel(child);
+                    if (panel != null)
+                    {
+                        object temp = panel;
+                        return (TabPanel)temp; // return the panel up the call stack
+                    }
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Clone an element
+        /// </summary>
+        /// <param name="elementToClone"></param>
+        /// <returns></returns>
+        public static object CloneElement(object elementToClone)
+        {
+            string xaml = XamlWriter.Save(elementToClone);
+            return XamlReader.Load(new XmlTextReader(new StringReader(xaml)));
+        }
+
+    }
+}

+ 785 - 0
src/YSAI.Controls/tabcontrol/TabControl.xaml

@@ -0,0 +1,785 @@
+<TabControl x:Class="YSAI.Controls.tabcontrol.TabControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:YSAI.Controls.tabcontrol">
+
+    <TabControl.Resources>
+        <ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:l="clr-namespace:YSAI.Controls.tabcontrol">
+
+            <BooleanToVisibilityConverter x:Key="boolConverter"/>
+            <l:InverseBooleanConverter x:Key="_inverseBooleanConverter"/>
+            <!-- 
+		Brushes used to define the Look of the TabControl & TabItem
+	-->
+            <!-- Border Brush for the TabItems and TabControl -->
+            <SolidColorBrush x:Key="TabBorderBrush" Color="LightGray"/>
+            <!-- Tab Text Colour when Not Selected-->
+            <SolidColorBrush x:Key="TabGrayTextBrush" Color="#FF444444"/>
+            <!-- TabItem CloseButton Brush-->
+            <SolidColorBrush x:Key="TabCloseButtonBrush" Color="#FFADADAD"/>
+
+            <!-- TabItem Brushes -->
+            <SolidColorBrush x:Key="TabItemNormalBackground" Color="WhiteSmoke"/>
+            <SolidColorBrush x:Key="TabItemHoverBackground" Color="LightGray"/>
+            <SolidColorBrush x:Key="TabItemSelectedBackground" Color="LightGray"/>
+
+            <!-- Style for the Repeat Button used to scrool the Tabs to the Left-->
+            <Style x:Key="RepeatButtonScrollLeftOrUpStyle" TargetType="{x:Type RepeatButton}">
+                <Setter Property="Foreground" Value="{StaticResource TabGrayTextBrush}" />
+                <Setter Property="Background" Value="{Binding Path=TabItemNormalBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type RepeatButton}">
+                            <Border x:Name="outerBorder"
+						Background="{TemplateBinding Background}"
+						BorderBrush="{TemplateBinding BorderBrush}"
+						BorderThickness="1"
+						Padding="{TemplateBinding Padding}"
+						CornerRadius="3,3,0,0">
+
+                                <Border x:Name="innerBorder" BorderThickness="1" BorderBrush="Transparent" Background="Transparent" Padding="4,0,4,0">
+                                    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
+                                        <Path x:Name="path1" Stroke="{TemplateBinding Foreground}" Fill="Transparent" Data="M 3,0 L0,3 3,6" />
+                                        <Path x:Name="path2" Stroke="{TemplateBinding Foreground}" Fill="Transparent" Data="M 7,0 L4,3 7,6" />
+                                    </Grid>
+                                </Border>
+
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                </Trigger>
+                                <Trigger Property="IsPressed" Value="True">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Background" TargetName="innerBorder" Value="#11000000"/>
+                                    <Setter Property="BorderBrush" TargetName="innerBorder" Value="#66000000" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="3,3,0,0"/>
+                                    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,2,1,0" />
+                                </Trigger>
+
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Left">
+                                    <Setter Property="Data" TargetName="path1" Value="M 0,3 L3,0 6,3"/>
+                                    <Setter Property="Data" TargetName="path2" Value="M 0,7 L3,4 6,7"/>
+                                </DataTrigger>
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Right">
+                                    <Setter Property="Data" TargetName="path1" Value="M 0,3 L3,0 6,3"/>
+                                    <Setter Property="Data" TargetName="path2" Value="M 0,7 L3,4 6,7"/>
+                                </DataTrigger>
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Bottom">
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- Style for the Repeat Button used to scrool the Tabs to the Right-->
+            <Style x:Key="RepeatButtonScrollRightOrDownStyle" TargetType="{x:Type RepeatButton}">
+                <Setter Property="Foreground" Value="{StaticResource TabGrayTextBrush}" />
+                <Setter Property="Background" Value="{Binding Path=TabItemNormalBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type RepeatButton}">
+                            <Border x:Name="outerBorder"
+						Background="{TemplateBinding Background}"
+						BorderBrush="{TemplateBinding BorderBrush}"
+						BorderThickness="1"
+						Padding="{TemplateBinding Padding}"
+						CornerRadius="3,3,0,0">
+                                <Border x:Name="innerBorder" BorderThickness="1" BorderBrush="Transparent" Background="Transparent" Padding="4,0,4,0">
+                                    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
+                                        <Path x:Name="path1" Stroke="{TemplateBinding Foreground}" Fill="Transparent" Data="M 0,0 L3,3 0,6" />
+                                        <Path x:Name="path2" Stroke="{TemplateBinding Foreground}" Fill="Transparent" Data="M 4,0 L7,3 4,6" />
+                                    </Grid>
+                                </Border>
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                </Trigger>
+                                <Trigger Property="IsPressed" Value="True">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Background" TargetName="innerBorder" Value="#11000000"/>
+                                    <Setter Property="BorderBrush" TargetName="innerBorder" Value="#66000000" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="3,3,0,0"/>
+                                    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,2,1,0" />
+                                </Trigger>
+
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Left">
+                                    <Setter Property="Data" TargetName="path1" Value="M 0,0 L3,3 6,0"/>
+                                    <Setter Property="Data" TargetName="path2" Value="M 0,4 L3,7 6,4"/>
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Right">
+                                    <Setter Property="Data" TargetName="path1" Value="M 0,0 L3,3 6,0"/>
+                                    <Setter Property="Data" TargetName="path2" Value="M 0,4 L3,7 6,4"/>
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Bottom">
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- Style for the Toggle Button which displays a cntext menu listing all the Tab Headers-->
+            <Style x:Key="DropDownToggleButtonStyle" TargetType="{x:Type ToggleButton}">
+                <Setter Property="Background" Value="{Binding Path=TabItemNormalBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="Foreground" Value="{StaticResource TabGrayTextBrush}"/>
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type ToggleButton}">
+                            <Border x:Name="outerBorder" CornerRadius="3,3,0,0" BorderThickness="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" >
+                                <Border x:Name="innerBorder" CornerRadius="3,3,0,0">
+                                    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False" Margin="0,1,0,0" SnapsToDevicePixels="False">
+                                        <Path Stroke="{TemplateBinding Foreground}" Data="M1,1L3,4 5,1z" Fill="{TemplateBinding Foreground}" ClipToBounds="False" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"/>
+                                    </Grid>
+                                </Border>
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                </Trigger>
+                                <Trigger Property="IsChecked" Value="True">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Background" TargetName="innerBorder" Value="#11000000"/>
+                                    <Setter Property="BorderBrush" TargetName="innerBorder" Value="#66000000" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="Margin" TargetName="innerBorder" Value="-1"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="3,3,0,0"/>
+                                    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,2,1,0" />
+                                </Trigger>
+
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Bottom">
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- Style for the New Tab Button -->
+            <Style x:Key="NewTabButtonStyle" TargetType="{x:Type Button}">
+                <Setter Property="Background" Value="{Binding Path=TabItemNormalBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="Foreground" Value="{StaticResource TabGrayTextBrush}"/>
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type Button}">
+                            <Border CornerRadius="3,3,0,0" BorderThickness="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" x:Name="outerBorder">
+                                <Border x:Name="innerBorder" CornerRadius="3,3,0,0">
+                                    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width="16" Height="16" Margin="0,1,0,0" SnapsToDevicePixels="False">
+                                        <ContentPresenter x:Name="Cp" Opacity="0.4" />
+                                    </Grid>
+                                </Border>
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="Opacity" TargetName="Cp" Value="1"/>
+                                </Trigger>
+                                <Trigger Property="IsPressed" Value="True">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Background" TargetName="innerBorder" Value="#11000000"/>
+                                    <Setter Property="BorderBrush" TargetName="innerBorder" Value="#66000000" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="Margin" TargetName="innerBorder" Value="-1"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="3,3,0,0"/>
+                                    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,2,1,0" />
+                                </Trigger>
+                                <DataTrigger Binding="{Binding Path=TabStripPlacement, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" Value="Bottom">
+                                    <Setter Property="CornerRadius" TargetName="outerBorder" Value="0,0,3,3"/>
+                                    <Setter Property="CornerRadius" TargetName="innerBorder" Value="0,0,3,3"/>
+                                </DataTrigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- Style for the Close Button on each TabItem -->
+            <Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}">
+                <Setter Property="BorderBrush" Value="Transparent"/>
+                <Setter Property="Background" Value="Transparent"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type Button}">
+                            <Border x:Name="border" 
+						CornerRadius="2" 
+						Background="{TemplateBinding Background}" 
+						BorderThickness="1" 
+						BorderBrush="{TemplateBinding BorderBrush}" 
+						Width="16" Height="16" 
+						SnapsToDevicePixels="True">
+                                <Grid Width="8" Height="8" HorizontalAlignment="Center" VerticalAlignment="Center">
+                                    <Path x:Name="path1" Stroke="{StaticResource TabCloseButtonBrush}" Data="M0,0 L8,8" StrokeThickness="2" />
+                                    <Path x:Name="path2" Stroke="{StaticResource TabCloseButtonBrush}" Data="M8,0 L0,8" StrokeThickness="2" />
+                                </Grid>
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="True">
+                                    <Setter Property="BorderBrush" Value="{StaticResource TabCloseButtonBrush}"/>
+                                    <Setter Property="Background" Value="WhiteSmoke"/>
+                                    <Setter Property="Stroke" TargetName="path1" Value="DarkRed"/>
+                                    <Setter Property="Stroke" TargetName="path2" Value="DarkRed"/>
+                                </Trigger>
+                                <Trigger Property="IsPressed" Value="True">
+                                    <Setter Property="Background">
+                                        <Setter.Value>
+                                            <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
+                                                <GradientStop Color="#FFADADAD" Offset="0"/>
+                                                <GradientStop Color="White" Offset="0.5"/>
+                                                <GradientStop Color="White" Offset="1"/>
+                                            </LinearGradientBrush>
+                                        </Setter.Value>
+                                    </Setter>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- TabItem Style, defines the look of a Tab Item-->
+            <Style TargetType="{x:Type l:TabItem}">
+                <Setter Property="Background" Value="{Binding Path=TabItemNormalBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="BorderBrush" Value="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}"/>
+                <Setter Property="Padding" Value="5" />
+                <Setter Property="HorizontalAlignment" Value="Stretch" />
+                <Setter Property="VerticalAlignment" Value="Stretch" />
+                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
+                <Setter Property="VerticalContentAlignment" Value="Stretch" />
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="{x:Type l:TabItem}">
+                            <Border x:Name="Bd"
+							Background="{TemplateBinding Background}"
+							BorderBrush="{TemplateBinding BorderBrush}"
+							SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
+                                <Grid HorizontalAlignment="Stretch">
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="Auto"/>
+                                        <ColumnDefinition Width="*"/>
+                                        <ColumnDefinition Width="Auto"/>
+                                    </Grid.ColumnDefinitions>
+
+                                    <ContentPresenter Content="{TemplateBinding Icon}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+
+                                    <Border Margin="2,0,2,0" Grid.Column="1">
+                                        <ContentPresenter
+									    Content="{TemplateBinding Header}"
+									    ContentTemplate="{TemplateBinding HeaderTemplate}"
+									    ContentSource="Header"
+										HorizontalAlignment="Stretch"
+										VerticalAlignment="Center"
+									    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
+									    Margin="{TemplateBinding Padding}"
+									    RecognizesAccessKey="True"/>
+                                    </Border>
+
+                                    <Button x:Name="PART_CloseButton" 
+								Grid.Column="2" 
+								VerticalAlignment="Center"
+								HorizontalAlignment="Center"
+								Margin="5,0,5,0"
+								Style="{StaticResource CloseButtonStyle}"
+								Visibility="Visible"
+								/>
+                                </Grid>
+                            </Border>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsMouseOver" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemMouseOverBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                </Trigger>
+                                <Trigger Property="IsSelected" Value="true">
+                                    <Setter Property="Background" Value="{Binding Path=TabItemSelectedBackground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:TabControl}}}" />
+                                    <Setter Property="Foreground" Value="Black"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0"/>
+                                </Trigger>
+
+                                <Trigger Property="TabStripPlacement" Value="Top">
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,1,0"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="4,4,0,0"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,7,0,-1"/>
+                                </Trigger>
+
+                                <Trigger Property="TabStripPlacement" Value="Bottom">
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,0,1,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="0,0,4,4"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,-1,0,7"/>
+                                </Trigger>
+
+                                <Trigger Property="TabStripPlacement" Value="Left">
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,0,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="4,0,0,0"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,0,0,-1"/>
+                                </Trigger>
+
+                                <Trigger Property="TabStripPlacement" Value="Right">
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="0,1,1,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="0,4,0,0"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,0,0,-1"/>
+                                </Trigger>
+
+                                <Trigger Property="AllowDelete" Value="false">
+                                    <Setter Property="Visibility" TargetName="PART_CloseButton" Value="Collapsed"/>
+                                </Trigger>
+
+                                <MultiTrigger>
+                                    <MultiTrigger.Conditions>
+                                        <Condition Property="TabStripPlacement" Value="Top"/>
+                                        <Condition Property="IsSelected" Value="true"/>
+                                    </MultiTrigger.Conditions>
+                                    <Setter Property="Panel.ZIndex" Value="2" />
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,1,0"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="4,4,0,0"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,0,0,-1"/>
+                                </MultiTrigger>
+
+                                <MultiTrigger>
+                                    <MultiTrigger.Conditions>
+                                        <Condition Property="TabStripPlacement" Value="Bottom"/>
+                                        <Condition Property="IsSelected" Value="true"/>
+                                    </MultiTrigger.Conditions>
+                                    <Setter Property="Panel.ZIndex" Value="2" />
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,0,1,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="0,0,4,4"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,-1,0,0"/>
+                                </MultiTrigger>
+
+                                <MultiTrigger>
+                                    <MultiTrigger.Conditions>
+                                        <Condition Property="TabStripPlacement" Value="Left"/>
+                                        <Condition Property="IsSelected" Value="true"/>
+                                    </MultiTrigger.Conditions>
+                                    <Setter Property="Panel.ZIndex" Value="2" />
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,0,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="4,0,0,4"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,0,0,-1"/>
+                                </MultiTrigger>
+
+                                <MultiTrigger>
+                                    <MultiTrigger.Conditions>
+                                        <Condition Property="TabStripPlacement" Value="Right"/>
+                                        <Condition Property="IsSelected" Value="true"/>
+                                    </MultiTrigger.Conditions>
+                                    <Setter Property="Panel.ZIndex" Value="2" />
+                                    <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,1,1"/>
+                                    <Setter Property="CornerRadius" TargetName="Bd" Value="0,4,0,0"/>
+                                    <Setter Property="Margin" TargetName="Bd" Value="0,0,0,-1"/>
+                                </MultiTrigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <!-- Control Template for TabStripPlacement Top-->
+            <ControlTemplate x:Key="TabControlTabPlacementTop" 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="Auto" />
+                        <RowDefinition x:Name="RowDefinition1" Height="*" />
+                    </Grid.RowDefinitions>
+
+                    <Border Grid.ColumnSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
+
+                    <!-- 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" 
+                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"/>
+
+                    <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>
+
+                    <!-- Repeat Buttons used for scrolling the TabItems into view-->
+                    <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}}" />
+
+                    <!-- Content Panel-->
+                    <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>
+
+            <!-- Control Template for 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>
+                </Grid>
+            </ControlTemplate>
+
+            <!-- Control Template for TabStripPlacement Left-->
+            <ControlTemplate x:Key="TabControlTabPlacementLeft" TargetType="{x:Type l:TabControl}">
+                <Grid SnapsToDevicePixels="{Binding Path=SnapsToDevicePixels, RelativeSource={RelativeSource TemplatedParent}}" KeyboardNavigation.TabNavigation="Local">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="20"/>
+                        <RowDefinition Height="*" />
+                        <RowDefinition Height="20"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="*" />
+                    </Grid.ColumnDefinitions>
+
+                    <Border Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"/>
+
+                    <Grid Grid.Row="0">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*" />
+                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="Auto" />
+                        </Grid.ColumnDefinitions>
+
+                        <!-- Repeat Buttons used for scrolling the TabItems into view-->
+                        <RepeatButton 
+                    x:Name="PART_RepeatLeft" 
+                    Panel.ZIndex="1" 
+                    Grid.Column="0" 
+                    Style="{StaticResource RepeatButtonScrollLeftOrUpStyle}" 
+                    Margin="0,0,0,-1" 
+                    ToolTip="Scroll the Tab Items to the Top" 
+                    Visibility="{Binding ElementName=TabPanel, Path=CanScrollLeftOrUp, Converter={StaticResource boolConverter}}"/>
+
+                        <!-- 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.Column="1" Margin="-1,0,0,-1" Style="{StaticResource DropDownToggleButtonStyle}" ToolTip="Display all the tabs in a menu" Width="20" Panel.ZIndex="1"
+                              Visibility="{Binding IsUsingItemsSource, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource _inverseBooleanConverter}}"/>
+
+                        <Button x:Name="PART_NewTabButton" Grid.Column="2" Margin="-1,0,0,-1" Style="{StaticResource NewTabButtonStyle}" Width="24"  Panel.ZIndex="1"
+				    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>
+                    </Grid>
+
+                    <ScrollViewer x:Name="PART_ScrollViewer" Grid.Row="1" Panel.ZIndex="1" CanContentScroll="True" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Disabled">
+                        <l:TabPanel x:Name="TabPanel" IsItemsHost="True" />
+                    </ScrollViewer>
+
+                    <RepeatButton 
+                x:Name="PART_RepeatRight" 
+                Panel.ZIndex="1" 
+                Grid.Row="2" 
+                Style="{StaticResource RepeatButtonScrollRightOrDownStyle}" 
+                ToolTip="Scroll the Tab Items to the Bottom" 
+                Visibility="{Binding ElementName=TabPanel, Path=CanScrollRightOrDown, Converter={StaticResource boolConverter}}"/>
+
+                    <!-- Content Panel-->
+                    <Border x:Name="ContentPanel"
+				Grid.Column="1" Grid.RowSpan="3"
+				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>
+                </Grid>
+            </ControlTemplate>
+
+            <!-- Control Template for TabStripPlacement Right-->
+            <ControlTemplate x:Key="TabControlTabPlacementRight" TargetType="{x:Type l:TabControl}">
+                <Grid SnapsToDevicePixels="{Binding Path=SnapsToDevicePixels, RelativeSource={RelativeSource TemplatedParent}}" KeyboardNavigation.TabNavigation="Local">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="20"/>
+                        <RowDefinition Height="*" />
+                        <RowDefinition Height="20"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*" />
+                        <ColumnDefinition Width="Auto" />
+                    </Grid.ColumnDefinitions>
+
+                    <Border Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"/>
+
+                    <Grid Grid.Row="0" Grid.Column="1">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+                        <!-- Repeat Buttons used for scrolling the TabItems into view-->
+                        <RepeatButton 
+                    x:Name="PART_RepeatLeft" 
+                    Panel.ZIndex="1" 
+                    Grid.Column="2" 
+                    Style="{StaticResource RepeatButtonScrollLeftOrUpStyle}" 
+                    Margin="0,0,0,-1" 
+                    ToolTip="Scroll the Tab Items to the Top" 
+                    Visibility="{Binding ElementName=TabPanel, Path=CanScrollLeftOrUp, Converter={StaticResource boolConverter}}"/>
+
+                        <!-- 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.Column="1" Margin="-1,0,0,-1" Style="{StaticResource DropDownToggleButtonStyle}" ToolTip="Display all the tabs in a menu" Width="20" Panel.ZIndex="1"
+                              Visibility="{Binding IsUsingItemsSource, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource _inverseBooleanConverter}}"/>
+
+                        <Button x:Name="PART_NewTabButton" Grid.Column="0" Margin="-1,0,0,-1" Style="{StaticResource NewTabButtonStyle}" Width="24"  Panel.ZIndex="1"
+				    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>
+                    </Grid>
+
+                    <ScrollViewer x:Name="PART_ScrollViewer" Grid.Row="1" Grid.Column="1" Panel.ZIndex="1" CanContentScroll="True" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Disabled">
+                        <l:TabPanel x:Name="TabPanel" IsItemsHost="True" />
+                    </ScrollViewer>
+
+                    <RepeatButton 
+                x:Name="PART_RepeatRight" 
+                Panel.ZIndex="1" 
+                Grid.Row="2" 
+                Grid.Column="1"
+                Style="{StaticResource RepeatButtonScrollRightOrDownStyle}" 
+                ToolTip="Scroll the Tab Items to the Bottom" 
+                Visibility="{Binding ElementName=TabPanel, Path=CanScrollRightOrDown, Converter={StaticResource boolConverter}}"/>
+
+                    <!-- Content Panel-->
+                    <Border x:Name="ContentPanel"
+				Grid.RowSpan="3"
+				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>
+                </Grid>
+            </ControlTemplate>
+            <!-- TabControl Style, defines the look of the TabControl-->
+            <Style TargetType="{x:Type l:TabControl}">
+                <Setter Property="KeyboardNavigation.TabNavigation" Value="Cycle"/>
+                <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle"/>
+
+                <Setter Property="Background" Value="White"/>
+                <Setter Property="BorderBrush" Value="{StaticResource TabBorderBrush}"/>
+                <Setter Property="BorderThickness" Value="1"/>
+
+                <Setter Property="TabItemNormalBackground" Value="{StaticResource TabItemNormalBackground}"/>
+                <Setter Property="TabItemMouseOverBackground" Value="{StaticResource TabItemHoverBackground}"/>
+                <Setter Property="TabItemSelectedBackground" Value="{StaticResource TabItemSelectedBackground}"/>
+
+                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
+                <Setter Property="VerticalContentAlignment" Value="Stretch" />
+
+                <Setter Property="SnapsToDevicePixels" Value="True"/>
+                <Setter Property="Template" Value="{StaticResource TabControlTabPlacementTop}"/>
+                <Style.Triggers>
+
+                    <Trigger Property="TabStripPlacement" Value="Bottom">
+                        <Setter Property="Template" Value="{StaticResource TabControlTabPlacementBottom}"/>
+                    </Trigger>
+
+                    <Trigger Property="TabStripPlacement" Value="Left">
+                        <Setter Property="TabItemMinWidth" Value="0"/>
+                        <Setter Property="TabItemMaxWidth" Value="150"/>
+
+                        <Setter Property="TabItemMinHeight" Value="20"/>
+                        <Setter Property="TabItemMaxHeight" Value="250"/>
+                        <Setter Property="Template" Value="{StaticResource TabControlTabPlacementLeft}"/>
+                    </Trigger>
+
+                    <Trigger Property="TabStripPlacement" Value="Right">
+                        <Setter Property="TabItemMinWidth" Value="0"/>
+                        <Setter Property="TabItemMaxWidth" Value="150"/>
+
+                        <Setter Property="TabItemMinHeight" Value="20"/>
+                        <Setter Property="TabItemMaxHeight" Value="250"/>
+                        <Setter Property="Template" Value="{StaticResource TabControlTabPlacementRight}"/>
+                    </Trigger>
+                </Style.Triggers>
+            </Style>
+        </ResourceDictionary>
+    </TabControl.Resources>
+</TabControl>

+ 774 - 0
src/YSAI.Controls/tabcontrol/TabControl.xaml.cs

@@ -0,0 +1,774 @@
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace YSAI.Controls.tabcontrol
+{
+    [TemplatePart(Name = "PART_DropDown", Type = typeof(ToggleButton))]
+    [TemplatePart(Name = "PART_RepeatLeft", Type = typeof(RepeatButton))]
+    [TemplatePart(Name = "PART_RepeatRight", Type = typeof(RepeatButton))]
+    [TemplatePart(Name = "PART_NewTabButton", Type = typeof(ButtonBase))]
+    [TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
+    public partial class TabControl : System.Windows.Controls.TabControl
+    {
+
+        // public Events
+        #region Events
+
+        public event EventHandler<CancelEventArgs> TabItemAdding;
+        public event EventHandler<TabItemEventArgs> TabItemAdded;
+        public event EventHandler<TabItemCancelEventArgs> TabItemClosing;
+        public event EventHandler<TabItemEventArgs> TabItemClosed;
+        public event EventHandler<NewTabItemEventArgs> NewTabItem;
+
+        #endregion
+
+        // TemplatePart controls
+        private ToggleButton _toggleButton;
+        private ButtonBase _addNewButton;
+
+        static TabControl()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(TabControl), new FrameworkPropertyMetadata(typeof(TabControl)));
+            TabStripPlacementProperty.AddOwner(typeof(TabControl), new FrameworkPropertyMetadata(Dock.Top, new PropertyChangedCallback(OnTabStripPlacementChanged)));
+        }
+
+        public TabControl()
+        {
+            InitializeComponent();
+            Loaded +=
+                delegate
+                {
+                    SetAddNewButtonVisibility();
+                    SetTabItemsCloseButtonVisibility();
+                    IsUsingItemsSource = BindingOperations.IsDataBound(this, ItemsSourceProperty);
+
+                    if (IsUsingItemsSource && IsFixedSize)
+                        AllowAddNew = AllowDelete = false;
+                };
+        }
+
+        #region Properties
+
+        private bool IsFixedSize
+        {
+            get
+            {
+                IEnumerable items = GetItems();
+                return items as IList == null || (items as IList).IsFixedSize;
+            }
+        }
+
+        #endregion
+
+        #region Dependancy properties
+
+        public bool IsUsingItemsSource
+        {
+            get { return (bool)GetValue(IsUsingItemsSourceProperty); }
+            private set { SetValue(IsUsingItemsSourcePropertyKey, value); }
+        }
+
+        public static readonly DependencyPropertyKey IsUsingItemsSourcePropertyKey =
+            DependencyProperty.RegisterReadOnly("IsUsingItemsSource", typeof(bool), typeof(TabControl), new UIPropertyMetadata(false));
+
+        public static readonly DependencyProperty IsUsingItemsSourceProperty = IsUsingItemsSourcePropertyKey.DependencyProperty;
+
+        #region Brushes
+
+        public Brush TabItemNormalBackground
+        {
+            get { return (Brush)GetValue(TabItemNormalBackgroundProperty); }
+            set { SetValue(TabItemNormalBackgroundProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemNormalBackgroundProperty = DependencyProperty.Register("TabItemNormalBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null));
+
+        public Brush TabItemMouseOverBackground
+        {
+            get { return (Brush)GetValue(TabItemMouseOverBackgroundProperty); }
+            set { SetValue(TabItemMouseOverBackgroundProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemMouseOverBackgroundProperty = DependencyProperty.Register("TabItemMouseOverBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null));
+
+        public Brush TabItemSelectedBackground
+        {
+            get { return (Brush)GetValue(TabItemSelectedBackgroundProperty); }
+            set { SetValue(TabItemSelectedBackgroundProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemSelectedBackgroundProperty = DependencyProperty.Register("TabItemSelectedBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null));
+
+
+
+
+        #endregion
+
+        /*
+         * Based on the whether the ControlTemplate implements the NewTab button and Close Buttons determines the functionality of the AllowAddNew & AllowDelete properties
+         * If they are in the control template, then the visibility of the AddNew & Header buttons are bound to these properties
+         * 
+        */
+        /// <summary>
+        /// Allow the User to Add New TabItems
+        /// </summary>
+        public bool AllowAddNew
+        {
+            get { return (bool)GetValue(AllowAddNewProperty); }
+            set { SetValue(AllowAddNewProperty, value); }
+        }
+        public static readonly DependencyProperty AllowAddNewProperty = DependencyProperty.Register("AllowAddNew", typeof(bool), typeof(TabControl),
+            new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAllowAddNewChanged), OnCoerceAllowAddNewCallback));
+
+        private static void OnAllowAddNewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            ((TabControl)d).SetAddNewButtonVisibility();
+        }
+
+        private static object OnCoerceAllowAddNewCallback(DependencyObject d, object basevalue)
+        {
+            return ((TabControl)d).OnCoerceAllowAddNewCallback(basevalue);
+        }
+
+        private object OnCoerceAllowAddNewCallback(object basevalue)
+        {
+            if (ItemsSource != null)
+            {
+                IList list = ItemsSource as IList;
+                if (list != null)
+                {
+                    if (list.IsFixedSize)
+                        return false;
+                    return basevalue;
+                }
+                return false;
+            }
+            return basevalue;
+        }
+
+        /// <summary>
+        /// Allow the User to Delete TabItems
+        /// </summary>
+        public bool AllowDelete
+        {
+            get { return (bool)GetValue(AllowDeleteProperty); }
+            set { SetValue(AllowDeleteProperty, value); }
+        }
+        public static readonly DependencyProperty AllowDeleteProperty = DependencyProperty.Register("AllowDelete", typeof(bool), typeof(TabControl),
+            new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAllowDeleteChanged), OnCoerceAllowDeleteNewCallback));
+
+        private static void OnAllowDeleteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            TabControl tc = (TabControl)d;
+            tc.SetTabItemsCloseButtonVisibility();
+        }
+
+        private static object OnCoerceAllowDeleteNewCallback(DependencyObject d, object basevalue)
+        {
+            return ((TabControl)d).OnCoerceAllowDeleteCallback(basevalue);
+        }
+        private object OnCoerceAllowDeleteCallback(object basevalue)
+        {
+            if (ItemsSource != null)
+            {
+                IList list = ItemsSource as IList;
+                if (list != null)
+                {
+                    if (list.IsFixedSize)
+                        return false;
+                    return basevalue;
+                }
+                return false;
+            }
+            return basevalue;
+        }
+
+        /// <summary>
+        /// Set new Header as the current selection
+        /// </summary>
+        public bool SelectNewTabOnCreate
+        {
+            get { return (bool)GetValue(SelectNewTabOnCreateProperty); }
+            set { SetValue(SelectNewTabOnCreateProperty, value); }
+        }
+        public static readonly DependencyProperty SelectNewTabOnCreateProperty = DependencyProperty.Register("SelectNewTabOnCreate", typeof(bool), typeof(TabControl), new UIPropertyMetadata(true));
+
+
+        /// <summary>
+        /// Determines where new TabItems are added to the TabControl
+        /// </summary>
+        /// <remarks>
+        ///     Set to true (default) to add all new Tabs to the end of the TabControl
+        ///     Set to False to insert new tabs after the current selection
+        /// </remarks>
+        public bool AddNewTabToEnd
+        {
+            get { return (bool)GetValue(AddNewTabToEndProperty); }
+            set { SetValue(AddNewTabToEndProperty, value); }
+        }
+        public static readonly DependencyProperty AddNewTabToEndProperty = DependencyProperty.Register("AddNewTabToEnd", typeof(bool), typeof(TabControl), new UIPropertyMetadata(true));
+
+        /// <summary>
+        /// defines the Minimum width of a Header
+        /// </summary>
+        [DefaultValue(20.0)]
+        [Category("Layout")]
+        [Description("Gets or Sets the minimum Width Constraint shared by all Items in the Control, individual child elements MinWidth property will overide this property")]
+        public double TabItemMinWidth
+        {
+            get { return (double)GetValue(TabItemMinWidthProperty); }
+            set { SetValue(TabItemMinWidthProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemMinWidthProperty = DependencyProperty.Register("TabItemMinWidth", typeof(double), typeof(TabControl),
+            new FrameworkPropertyMetadata(20.0, new PropertyChangedCallback(OnMinMaxChanged), CoerceMinWidth));
+
+        private static object CoerceMinWidth(DependencyObject d, object value)
+        {
+            TabControl tc = (TabControl)d;
+            double newValue = (double)value;
+
+            if (newValue > tc.TabItemMaxWidth)
+                return tc.TabItemMaxWidth;
+
+            return (newValue > 0 ? newValue : 0);
+        }
+
+        /// <summary>
+        /// defines the Minimum height of a Header
+        /// </summary>
+        [DefaultValue(20.0)]
+        [Category("Layout")]
+        [Description("Gets or Sets the minimum Height Constraint shared by all Items in the Control, individual child elements MinHeight property will override this value")]
+        public double TabItemMinHeight
+        {
+            get { return (double)GetValue(TabItemMinHeightProperty); }
+            set { SetValue(TabItemMinHeightProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemMinHeightProperty = DependencyProperty.Register("TabItemMinHeight", typeof(double), typeof(TabControl),
+            new FrameworkPropertyMetadata(20.0, new PropertyChangedCallback(OnMinMaxChanged), CoerceMinHeight));
+
+        private static object CoerceMinHeight(DependencyObject d, object value)
+        {
+            TabControl tc = (TabControl)d;
+            double newValue = (double)value;
+
+            if (newValue > tc.TabItemMaxHeight)
+                return tc.TabItemMaxHeight;
+
+            return (newValue > 0 ? newValue : 0);
+        }
+
+        /// <summary>
+        /// defines the Maximum width of a Header
+        /// </summary>
+        [DefaultValue(double.PositiveInfinity)]
+        [Category("Layout")]
+        [Description("Gets or Sets the maximum width Constraint shared by all Items in the Control, individual child elements MaxWidth property will override this value")]
+        public double TabItemMaxWidth
+        {
+            get { return (double)GetValue(TabItemMaxWidthProperty); }
+            set { SetValue(TabItemMaxWidthProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemMaxWidthProperty = DependencyProperty.Register("TabItemMaxWidth", typeof(double), typeof(TabControl),
+            new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnMinMaxChanged), CoerceMaxWidth));
+
+        private static object CoerceMaxWidth(DependencyObject d, object value)
+        {
+            TabControl tc = (TabControl)d;
+            double newValue = (double)value;
+
+            if (newValue < tc.TabItemMinWidth)
+                return tc.TabItemMinWidth;
+
+            return newValue;
+        }
+
+        /// <summary>
+        /// defines the Maximum width of a Header
+        /// </summary>
+        [DefaultValue(double.PositiveInfinity)]
+        [Category("Layout")]
+        [Description("Gets or Sets the maximum height Constraint shared by all Items in the Control, individual child elements MaxHeight property will override this value")]
+        public double TabItemMaxHeight
+        {
+            get { return (double)GetValue(TabItemMaxHeightProperty); }
+            set { SetValue(TabItemMaxHeightProperty, value); }
+        }
+        public static readonly DependencyProperty TabItemMaxHeightProperty = DependencyProperty.Register("TabItemMaxHeight", typeof(double), typeof(TabControl),
+            new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnMinMaxChanged), CoerceMaxHeight));
+
+        private static object CoerceMaxHeight(DependencyObject d, object value)
+        {
+            TabControl tc = (TabControl)d;
+            double newValue = (double)value;
+
+            if (newValue < tc.TabItemMinHeight)
+                return tc.TabItemMinHeight;
+
+            return newValue;
+        }
+
+        /// <summary>
+        /// OnMinMaxChanged callback responds to any of the Min/Max dependancy properties changing
+        /// </summary>
+        /// <param name="d"></param>
+        /// <param name="e"></param>
+        private static void OnMinMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            TabControl tc = (TabControl)d;
+            if (tc.Template == null) return;
+
+            foreach (TabItem child in tc.InternalChildren())
+            {
+                if (child != null)
+                    child.Dimension = null;
+            }
+
+
+            //            var tabsCount = tc.GetTabsCount();
+            //            for (int i = 0; i < tabsCount; i++)
+            //            {
+            //                Header ti = tc.GetTabItem(i);
+            //                if (ti != null)
+            //                    ti.Dimension = null;
+            //            }
+
+            TabPanel tp = Helper.FindVirtualizingTabPanel(tc);
+            if (tp != null)
+                tp.InvalidateMeasure();
+        }
+
+        /// <summary>
+        /// OnTabStripPlacementChanged property callback
+        /// </summary>
+        /// <remarks>
+        ///     We need to supplement the base implementation with this method as the base method does not work when
+        ///     we are using virtualization in the tabpanel, it only updates visible items
+        /// </remarks>
+        private static void OnTabStripPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            TabControl tc = (TabControl)d;
+
+            foreach (TabItem tabItem in tc.InternalChildren())
+            {
+                if (tabItem != null)
+                {
+                    tabItem.Dimension = null;
+                    tabItem.CoerceValue(TabItem.TabStripPlacementProperty);
+                }
+            }
+        }
+
+        #endregion
+
+        /*
+         * Protected override methods
+         * 
+        */
+
+        #region Overrides
+
+        /// <summary>
+        /// OnApplyTemplate override
+        /// </summary>
+        public override void OnApplyTemplate()
+        {
+            base.OnApplyTemplate();
+
+            // set up the event handler for the template parts
+            _toggleButton = this.Template.FindName("PART_DropDown", this) as ToggleButton;
+            if (_toggleButton != null)
+            {
+                // create a context menu for the togglebutton
+                ContextMenu cm = new ContextMenu { PlacementTarget = _toggleButton, Placement = PlacementMode.Bottom };
+
+                // create a binding between the togglebutton's IsChecked Property
+                // and the Context Menu's IsOpen Property
+                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();
+                };
+            }
+
+            // set up the event handler for the 'New Tab' Button Click event
+            _addNewButton = this.Template.FindName("PART_NewTabButton", this) as ButtonBase;
+            if (_addNewButton != null)
+                _addNewButton.Click += ((sender, routedEventArgs) => AddTabItem());
+        }
+
+        /// <summary>
+        /// IsItemItsOwnContainerOverride
+        /// </summary>
+        /// <param name="item"></param>
+        /// <returns></returns>
+        protected override bool IsItemItsOwnContainerOverride(object item)
+        {
+            return item is TabItem;
+        }
+        /// <summary>
+        /// GetContainerForItemOverride
+        /// </summary>
+        /// <returns></returns>
+        protected override DependencyObject GetContainerForItemOverride()
+        {
+            return new TabItem();
+        }
+
+
+        protected override void OnPreviewKeyDown(KeyEventArgs e)
+        {
+            var tabsCount = GetTabsCount();
+            if (tabsCount == 0)
+                return;
+
+            TabItem ti = null;
+
+            switch (e.Key)
+            {
+                case Key.Home:
+                    ti = GetTabItem(0);
+                    break;
+
+                case Key.End:
+                    ti = GetTabItem(tabsCount - 1);
+                    break;
+
+                case Key.Tab:
+                    if (e.KeyboardDevice.Modifiers == ModifierKeys.Control)
+                    {
+                        var index = SelectedIndex;
+                        var direction = e.KeyboardDevice.Modifiers == ModifierKeys.Shift ? -1 : 1;
+
+                        while (true)
+                        {
+                            index += direction;
+                            if (index < 0)
+                                index = tabsCount - 1;
+                            else if (index > tabsCount - 1)
+                                index = 0;
+
+                            FrameworkElement ui = GetTabItem(index);
+                            if (ui != null)
+                            {
+                                if (ui.Visibility == Visibility.Visible && ui.IsEnabled)
+                                {
+                                    ti = GetTabItem(index);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    break;
+            }
+
+            TabPanel panel = Helper.FindVirtualizingTabPanel(this);
+            if (panel != null && ti != null)
+            {
+                panel.MakeVisible(ti, Rect.Empty);
+                SelectedItem = ti;
+
+                e.Handled = ti.Focus();
+            }
+            base.OnPreviewKeyDown(e);
+        }
+
+        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
+        {
+            base.OnItemsChanged(e);
+
+            if (e.Action == NotifyCollectionChangedAction.Add && SelectNewTabOnCreate)
+            {
+                TabItem tabItem = (TabItem)this.ItemContainerGenerator.ContainerFromItem(e.NewItems[e.NewItems.Count - 1]);
+                SelectedItem = tabItem;
+
+                TabPanel itemsHost = Helper.FindVirtualizingTabPanel(this);
+                if (itemsHost != null)
+                    itemsHost.MakeVisible(tabItem, Rect.Empty);
+
+                tabItem.Focus();
+            }
+        }
+
+        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
+        {
+            base.OnItemsSourceChanged(oldValue, newValue);
+
+            IsUsingItemsSource = newValue != null;
+            if (IsFixedSize)
+                AllowAddNew = AllowDelete = false;
+
+            SetAddNewButtonVisibility();
+            SetTabItemsCloseButtonVisibility();
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Handle the ToggleButton Checked event that displays a context menu of Header Headers
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        void DropdownButton_Checked(object sender, RoutedEventArgs e)
+        {
+            if (_toggleButton == null) return;
+
+            _toggleButton.ContextMenu.Items.Clear();
+            _toggleButton.ContextMenu.Placement = TabStripPlacement == Dock.Bottom ? PlacementMode.Top : PlacementMode.Bottom;
+
+            int index = 0;
+            foreach (TabItem tabItem in this.InternalChildren())
+            {
+                if (tabItem != null)
+                {
+                    var header = Helper.CloneElement(tabItem.Header);
+                    var icon = tabItem.Icon == null ? null : Helper.CloneElement(tabItem.Icon);
+
+                    var mi = new MenuItem { Header = header, Icon = icon, Tag = index++.ToString() };
+                    mi.Click += ContextMenuItem_Click;
+
+                    _toggleButton.ContextMenu.Items.Add(mi);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handle the MenuItem's Click event
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        void ContextMenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            MenuItem mi = sender as MenuItem;
+            if (mi == null) return;
+
+            int index;
+            // get the index of the Header from the manuitems Tag property
+            bool b = int.TryParse(mi.Tag.ToString(), out index);
+
+            if (b)
+            {
+                TabItem tabItem = GetTabItem(index);
+                if (tabItem != null)
+                {
+                    TabPanel itemsHost = Helper.FindVirtualizingTabPanel(this);
+                    if (itemsHost != null)
+                        itemsHost.MakeVisible(tabItem, Rect.Empty);
+
+                    tabItem.Focus();
+                }
+            }
+        }
+
+        /// <summary>
+        ///     Add a new Header
+        /// </summary>
+        public void AddTabItem()
+        {
+            if (IsFixedSize)
+                throw new InvalidOperationException("ItemsSource is Fixed Size");
+
+            int i = this.SelectedIndex;
+
+            // give an opertunity to cancel the adding of the tabitem
+            CancelEventArgs c = new CancelEventArgs();
+            if (TabItemAdding != null)
+                TabItemAdding(this, c);
+
+            if (c.Cancel)
+                return;
+
+            TabItem tabItem;
+
+            // Using ItemsSource property
+            if (ItemsSource != null)
+            {
+                IList list = (IList)ItemsSource;
+                NewTabItemEventArgs n = new NewTabItemEventArgs();
+                if (NewTabItem == null)
+                    throw new InvalidOperationException("You must implement the NewTabItem event to supply the item to be added to the tab control.");
+
+                NewTabItem(this, n);
+                if (n.Content == null)
+                    return;
+
+                if (i == -1 || i == list.Count - 1 || AddNewTabToEnd)
+                    list.Add(n.Content);
+                else
+                    list.Insert(++i, n.Content);
+
+                tabItem = (TabItem)this.ItemContainerGenerator.ContainerFromItem(n.Content);
+            }
+            else
+            {
+                // Using Items Property
+                tabItem = new TabItem { Header = "New Tab" };
+
+                if (i == -1 || i == this.Items.Count - 1 || AddNewTabToEnd)
+                    this.Items.Add(tabItem);
+                else
+                    this.Items.Insert(++i, tabItem);
+            }
+
+            if (TabItemAdded != null)
+                TabItemAdded(this, new TabItemEventArgs(tabItem));
+        }
+
+        /// <summary>
+        /// Called by a child Header that wants to remove itself by clicking on the close button
+        /// </summary>
+        /// <param name="tabItem"></param>
+        public void RemoveTabItem(TabItem tabItem)
+        {
+            if (IsFixedSize)
+                throw new InvalidOperationException("ItemsSource is Fixed Size");
+
+            // gives an opertunity to cancel the removal of the tabitem
+            var c = new TabItemCancelEventArgs(tabItem);
+            if (TabItemClosing != null)
+                TabItemClosing(tabItem, c);
+
+            if (c.Cancel)
+                return;
+
+            if (ItemsSource != null)
+            {
+                var list = ItemsSource as IList;
+                object listItem = ItemContainerGenerator.ItemFromContainer(tabItem);
+                if (listItem != null && list != null)
+                    list.Remove(listItem);
+            }
+            else
+                this.Items.Remove(tabItem);
+
+            if (TabItemClosed != null)
+                TabItemClosed(this, new TabItemEventArgs(tabItem));
+        }
+
+        private void SetAddNewButtonVisibility()
+        {
+            if (this.Template == null)
+                return;
+
+            ButtonBase button = this.Template.FindName("PART_NewTabButton", this) as ButtonBase;
+            if (button == null) return;
+
+            if (IsFixedSize)
+                button.Visibility = Visibility.Collapsed;
+            else
+                button.Visibility = AllowAddNew
+                                        ? Visibility.Visible
+                                        : Visibility.Collapsed;
+        }
+
+        private void SetTabItemsCloseButtonVisibility()
+        {
+            bool isFixedSize = IsFixedSize;
+
+            var tabsCount = GetTabsCount();
+            for (int i = 0; i < tabsCount; i++)
+            {
+                TabItem ti = GetTabItem(i);
+                if (ti != null)
+                    ti.AllowDelete = !isFixedSize && this.AllowDelete;
+            }
+        }
+
+        internal IEnumerable GetItems()
+        {
+            if (IsUsingItemsSource)
+                return ItemsSource;
+            return Items;
+        }
+
+        internal int GetTabsCount()
+        {
+            if (BindingOperations.IsDataBound(this, ItemsSourceProperty))
+            {
+                IList list = ItemsSource as IList;
+                if (list != null)
+                    return list.Count;
+
+                // ItemsSource is only an IEnumerable
+                int i = 0;
+                IEnumerator enumerator = ItemsSource.GetEnumerator();
+                while (enumerator.MoveNext())
+                    i++;
+                return i;
+            }
+
+            if (Items != null)
+                return Items.Count;
+
+            return 0;
+        }
+
+        internal TabItem GetTabItem(int index)
+        {
+            if (BindingOperations.IsDataBound(this, ItemsSourceProperty))
+            {
+                IList list = ItemsSource as IList;
+                if (list != null)
+                    return this.ItemContainerGenerator.ContainerFromItem(list[index]) as TabItem;
+
+                // ItemsSource is at least an IEnumerable
+                int i = 0;
+                IEnumerator enumerator = ItemsSource.GetEnumerator();
+                while (enumerator.MoveNext())
+                {
+                    if (i == index)
+                        return this.ItemContainerGenerator.ContainerFromItem(enumerator.Current) as TabItem;
+                    i++;
+                }
+                return null;
+            }
+            return Items[index] as TabItem;
+        }
+
+        private IEnumerable InternalChildren()
+        {
+            IEnumerator enumerator = GetItems().GetEnumerator();
+            while (enumerator.MoveNext())
+            {
+                if (enumerator.Current is TabItem)
+                    yield return enumerator.Current;
+                else
+                    yield return this.ItemContainerGenerator.ContainerFromItem(enumerator.Current) as TabItem;
+            }
+        }
+    }
+}

+ 0 - 10
src/YSAI.Controls/tabcontrol/TabControlControl.xaml

@@ -1,10 +0,0 @@
-<TabControl x:Class="YSAI.Controls.tabcontrol.TabControlControl"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:local="clr-namespace:YSAI.Controls.tabcontrol">
-
-    <TabControl.Resources>
-    </TabControl.Resources>
-</TabControl>

+ 0 - 15
src/YSAI.Controls/tabcontrol/TabControlControl.xaml.cs

@@ -1,15 +0,0 @@
-using System.Windows.Controls;
-
-namespace YSAI.Controls.tabcontrol
-{
-    /// <summary>
-    /// TabControlControl.xaml 的交互逻辑
-    /// </summary>
-    public partial class TabControlControl : TabControl
-    {
-        public TabControlControl()
-        {
-            InitializeComponent();
-        }
-    }
-}

+ 32 - 0
src/YSAI.Controls/tabcontrol/TabEventArgs.cs

@@ -0,0 +1,32 @@
+using System;
+using System.ComponentModel;
+
+namespace YSAI.Controls.tabcontrol
+{
+    public class TabItemEventArgs : EventArgs
+    {
+        public TabItem TabItem { get; private set; }
+
+        public TabItemEventArgs(TabItem item)
+        {
+            TabItem = item;
+        }
+    }
+    public class NewTabItemEventArgs : EventArgs
+    {
+        /// <summary>
+        ///     The object to be used as the Content for the new TabItem
+        /// </summary>
+        public object Content { get; set; }
+    }
+
+    public class TabItemCancelEventArgs : CancelEventArgs
+    {
+        public TabItem TabItem { get; private set; }
+
+        public TabItemCancelEventArgs(TabItem item)
+        {
+            TabItem = item;
+        }
+    }
+}

+ 86 - 0
src/YSAI.Controls/tabcontrol/TabItem.cs

@@ -0,0 +1,86 @@
+using System.Windows;
+using System.Windows.Controls.Primitives;
+
+namespace YSAI.Controls.tabcontrol
+{
+    [TemplatePart(Name = "PART_CloseButton", Type = typeof(ButtonBase))]
+    public class TabItem : System.Windows.Controls.TabItem
+    {
+        static TabItem()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(YSAI.Controls.tabcontrol.TabItem), new FrameworkPropertyMetadata(typeof(YSAI.Controls.tabcontrol.TabItem)));
+        }
+
+        /// <summary>
+        /// Provides a place to display an Icon on the Header and on the DropDown Context Menu
+        /// </summary>
+        public object Icon
+        {
+            get { return (object)GetValue(IconProperty); }
+            set { SetValue(IconProperty, value); }
+        }
+        public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(TabItem), new UIPropertyMetadata(null));
+
+        /// <summary>
+        /// Allow the Header to be Deleted by the end user
+        /// </summary>
+        public bool AllowDelete
+        {
+            get { return (bool)GetValue(AllowDeleteProperty); }
+            set { SetValue(AllowDeleteProperty, value); }
+        }
+        public static readonly DependencyProperty AllowDeleteProperty = DependencyProperty.Register("AllowDelete", typeof(bool), typeof(TabItem), new UIPropertyMetadata(true));
+
+        /// <summary>
+        /// OnApplyTemplate override
+        /// </summary>
+        public override void OnApplyTemplate()
+        {
+            base.OnApplyTemplate();
+
+            // wire up the CloseButton's Click event if the button exists
+            ButtonBase button = this.Template.FindName("PART_CloseButton", this) as ButtonBase;
+            if (button != null)
+            {
+                button.Click += delegate
+                {
+                    // get the parent tabcontrol 
+                    TabControl tc = Helper.FindParentControl<TabControl>(this);
+                    if (tc == null) return;
+
+                    // remove this tabitem from the parent tabcontrol
+                    tc.RemoveTabItem(this);
+                };
+            }
+        }
+
+        /// <summary>
+        ///     Used by the TabPanel for sizing
+        /// </summary>
+        internal Dimension Dimension { get; set; }
+
+        /// <summary>
+        /// OnMouseEnter, Create and Display a Tooltip
+        /// </summary>
+        /// <param name="e"></param>
+        protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
+        {
+            base.OnMouseEnter(e);
+
+            this.ToolTip = Helper.CloneElement(Header);
+            e.Handled = true;
+        }
+
+        /// <summary>
+        /// OnMouseLeave, remove the tooltip
+        /// </summary>
+        /// <param name="e"></param>
+        protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
+        {
+            base.OnMouseLeave(e);
+
+            this.ToolTip = null;
+            e.Handled = true;
+        }
+    }
+}

+ 742 - 0
src/YSAI.Controls/tabcontrol/TabPanel.cs

@@ -0,0 +1,742 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+
+namespace YSAI.Controls.tabcontrol
+{
+    /// <summary>
+    /// TabPanel
+    /// </summary>
+    public class TabPanel : Panel, IScrollInfo
+    {
+        private int _maxVisibleItems;
+        //        private double _maxChildWidthOrHeight;
+        private readonly List<Rect> _childRects;
+        private Dock _tabStripPlacement;
+
+        // scrolling
+        private readonly TranslateTransform _translateTransform = new TranslateTransform();
+        private Size _extent = new Size(0, 0);
+        private Size _oldExtent = new Size(0, 0);
+        private Size _viewPort = new Size(0, 0);
+        private Size _lastSize = new Size(0, 0);
+        private Point _offset = new Point(0, 0);
+        private ScrollViewer _scrollOwner;
+
+        private int _firstVisibleIndex;
+        //        private int _childCount;
+
+        public TabPanel()
+        {
+            _childRects = new List<Rect>(4);
+            base.RenderTransform = _translateTransform;
+        }
+
+        #region CLR Properties
+        private Dock TabStripPlacement
+        {
+            get
+            {
+                Dock dock = Dock.Top;
+                TabControl templatedParent = base.TemplatedParent as TabControl;
+                if (templatedParent != null)
+                {
+                    dock = templatedParent.TabStripPlacement;
+
+                }
+                return dock;
+            }
+        }
+        private double MinimumChildWidth
+        {
+            get
+            {
+                TabControl templatedParent = base.TemplatedParent as TabControl;
+                if (templatedParent != null)
+                    return templatedParent.TabItemMinWidth;
+                return 0;
+            }
+        }
+        private double MinimumChildHeight
+        {
+            get
+            {
+                TabControl templatedParent = base.TemplatedParent as TabControl;
+                if (templatedParent != null)
+                    return templatedParent.TabItemMinHeight;
+                return 0;
+            }
+        }
+        private double MaximumChildWidth
+        {
+            get
+            {
+                TabControl templatedParent = base.TemplatedParent as TabControl;
+                if (templatedParent != null)
+                    return templatedParent.TabItemMaxWidth;
+                return double.PositiveInfinity;
+            }
+        }
+        private double MaximumChildHeight
+        {
+            get
+            {
+                TabControl templatedParent = base.TemplatedParent as TabControl;
+                if (templatedParent != null)
+                    return templatedParent.TabItemMaxHeight;
+                return double.PositiveInfinity;
+            }
+        }
+
+        private int FirstVisibleIndex
+        {
+            get { return _firstVisibleIndex; }
+            set
+            {
+                if (_firstVisibleIndex == value)
+                    return;
+
+                if (value < 0)
+                {
+                    _firstVisibleIndex = 0;
+                    return;
+                }
+
+                _firstVisibleIndex = value;
+                if (LastVisibleIndex > InternalChildren.Count - 1)
+                    FirstVisibleIndex--;
+            }
+        }
+
+        private int LastVisibleIndex
+        {
+            get { return _firstVisibleIndex + _maxVisibleItems - 1; }
+        }
+        #endregion
+
+        #region Dependancy Properties
+
+        /// <summary>
+        /// CanScrollLeftOrUp Dependancy Property
+        /// </summary>
+        [Browsable(false)]
+        internal bool CanScrollLeftOrUp
+        {
+            get { return (bool)GetValue(CanScrollLeftOrUpProperty); }
+            set { SetValue(CanScrollLeftOrUpProperty, value); }
+        }
+        internal static readonly DependencyProperty CanScrollLeftOrUpProperty = DependencyProperty.Register("CanScrollLeftOrUp", typeof(bool), typeof(TabPanel), new UIPropertyMetadata(false));
+
+        /// <summary>
+        /// CanScrollRightOrDown Dependancy Property
+        /// </summary>
+        [Browsable(false)]
+        internal bool CanScrollRightOrDown
+        {
+            get { return (bool)GetValue(CanScrollRightOrDownProperty); }
+            set { SetValue(CanScrollRightOrDownProperty, value); }
+        }
+        internal static readonly DependencyProperty CanScrollRightOrDownProperty = DependencyProperty.Register("CanScrollRightOrDown", typeof(bool), typeof(TabPanel), new UIPropertyMetadata(false));
+
+        #endregion
+
+        #region MeasureOverride
+        /// <summary>
+        /// Measure Override
+        /// </summary>
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            _viewPort = availableSize;
+            _tabStripPlacement = TabStripPlacement;
+
+            switch (_tabStripPlacement)
+            {
+                case Dock.Top:
+                case Dock.Bottom:
+                    return MeasureHorizontal(availableSize);
+
+                case Dock.Left:
+                case Dock.Right:
+                    return MeasureVertical(availableSize);
+            }
+
+            return new Size();
+        }
+        #endregion
+
+        #region MeasureHorizontal
+        /// <summary>
+        /// Measure the tab items for docking at the top or bottom
+        /// </summary>
+        private Size MeasureHorizontal(Size availableSize)
+        {
+            double maxChildWidthOrHeight = 0d;
+            int childCount = InternalChildren.Count;
+            EnsureChildRects();
+
+            double extentWidth = 0d;
+            double[] widths = new double[childCount];  // stores the widths of the items for use in the arrange pass
+
+            for (int i = 0; i < childCount; i++)
+            {
+                TabItem tabItem = InternalChildren[i] as TabItem;
+                if (tabItem == null) return new Size();
+
+                SetDimensions(tabItem);
+
+                tabItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+
+                ClearDimensions(tabItem);
+
+                // calculate the maximum child height
+                maxChildWidthOrHeight = Math.Max(maxChildWidthOrHeight, Math.Ceiling(tabItem.DesiredSize.Height));
+
+                // calculate the child width while respecting the Maximum & Minimum width constraints
+                widths[i] = Math.Min(MaximumChildWidth, Math.Max(MinimumChildWidth, Math.Ceiling(tabItem.DesiredSize.Width)));
+
+                // determines how much horizontal space we require
+                extentWidth += widths[i];
+            }
+            maxChildWidthOrHeight = Math.Max(MinimumChildHeight, Math.Min(MaximumChildHeight, maxChildWidthOrHeight));  // observe the constraints
+            _extent = new Size(extentWidth, maxChildWidthOrHeight);
+
+            bool flag = false;
+            // 1). all the children fit into the available space using there desired widths
+            if (extentWidth <= availableSize.Width)
+            {
+                _maxVisibleItems = childCount;
+                FirstVisibleIndex = 0;
+
+                double left = 0;
+                for (int i = 0; i < childCount; i++)
+                {
+                    _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight);
+                    left += widths[i];
+
+                    FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                    if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight));
+                }
+
+                CanScrollLeftOrUp = false;
+                CanScrollRightOrDown = false;
+
+                flag = true;
+            }
+
+            // 2). all the children fit in the available space if we reduce their widths to a uniform value 
+            // while staying within the MinimumChildWidth and MaximumChildWidth constraints
+            if (!flag)
+            {
+                // make sure the width is not greater than the MaximumChildWidth constraints
+                double targetWidth = Math.Min(MaximumChildWidth, availableSize.Width / childCount);
+
+                // target width applies now if whether we can fit all items in the available space or whether we are scrolling
+                if (targetWidth >= MinimumChildWidth)
+                {
+                    _maxVisibleItems = childCount;
+                    FirstVisibleIndex = 0;
+
+                    extentWidth = 0;
+                    double left = 0;
+
+                    for (int i = 0; i < childCount; i++)
+                    {
+                        extentWidth += targetWidth;
+                        widths[i] = targetWidth;
+                        _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight);
+                        left += widths[i];
+
+                        FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                        if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight));
+                    }
+                    _extent = new Size(extentWidth, maxChildWidthOrHeight);
+
+                    flag = true;
+
+                    CanScrollLeftOrUp = false;
+                    CanScrollRightOrDown = false;
+                }
+            }
+
+            // 3) we can not fit all the children in the viewport, so now we will enable scrolling/virtualizing items
+            if (!flag)
+            {
+                _maxVisibleItems = (int)Math.Floor(_viewPort.Width / MinimumChildWidth);            // calculate how many visible children we can show at once
+                if (_maxVisibleItems == 0)
+                    _maxVisibleItems = 1;
+
+                double targetWidth = availableSize.Width / _maxVisibleItems;                        // calculate the new target width
+                FirstVisibleIndex = _firstVisibleIndex;
+
+                extentWidth = 0;
+                double left = 0;
+                for (int i = 0; i < childCount; i++)
+                {
+                    extentWidth += targetWidth;
+                    widths[i] = targetWidth;
+
+                    _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight);
+                    left += widths[i];
+
+
+                    FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                    if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight));
+                }
+                _extent = new Size(extentWidth, maxChildWidthOrHeight);
+
+                CanScrollLeftOrUp = LastVisibleIndex < childCount - 1;
+                CanScrollRightOrDown = FirstVisibleIndex > 0;
+            }
+
+            return new Size(double.IsInfinity(availableSize.Width) ? _extent.Width : availableSize.Width, maxChildWidthOrHeight);
+        }
+
+        #endregion
+
+        #region MeasureVertical
+
+        /// <summary>
+        /// Measure the tab items for docking at the left or right
+        /// </summary>
+        private Size MeasureVertical(Size availableSize)
+        {
+            int childCount = InternalChildren.Count;
+            double maxChildWidthOrHeight = 0d;
+
+            EnsureChildRects();
+
+            double extentHeight = 0d;
+            double[] heights = new double[childCount];
+
+            // we will first measure all the children with unlimited space to get their desired sizes
+            // this will also get us the height required for all TabItems
+            for (int i = 0; i < childCount; i++)
+            {
+                TabItem tabItem = InternalChildren[i] as TabItem;
+                if (tabItem == null) return new Size();
+
+                SetDimensions(tabItem);
+
+                tabItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+
+                ClearDimensions(tabItem);
+
+                // calculate the maximum child width
+                maxChildWidthOrHeight = Math.Max(maxChildWidthOrHeight, Math.Ceiling(tabItem.DesiredSize.Width));
+
+                // calculate the child width while respecting the Maximum & Minimum width constraints
+                heights[i] = Math.Min(MaximumChildHeight, Math.Max(MinimumChildHeight, Math.Ceiling(tabItem.DesiredSize.Height)));
+
+                // determines how much horizontal space we require
+                extentHeight += heights[i];
+            }
+            maxChildWidthOrHeight = Math.Max(MinimumChildWidth, Math.Min(MaximumChildWidth, maxChildWidthOrHeight));  // observe the constraints
+            _extent = new Size(maxChildWidthOrHeight, extentHeight);
+
+            bool flag = false;
+            // 1). all the children fit into the available space using there desired widths
+            if (extentHeight <= availableSize.Height)
+            {
+                _maxVisibleItems = childCount;
+                FirstVisibleIndex = 0;
+
+                double top = 0;
+                for (int i = 0; i < childCount; i++)
+                {
+                    _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]);
+                    top += heights[i];
+
+                    FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                    if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i]));
+                }
+
+                CanScrollLeftOrUp = false;
+                CanScrollRightOrDown = false;
+
+                flag = true;
+            }
+
+            // 2). all the children fit in the available space if we reduce their widths to a uniform value 
+            // while staying within the MinimumChildWidth and MaximumChildWidth constraints
+            if (!flag)
+            {
+                // make sure the width is not greater than the MaximumChildWidth constraints
+                double targetHeight = Math.Min(MaximumChildHeight, availableSize.Height / childCount);
+
+                // target width applies now if whether we can fit all items in the available space or whether we are scrolling
+                if (targetHeight >= MinimumChildHeight)
+                {
+                    _maxVisibleItems = childCount;
+                    FirstVisibleIndex = 0;
+
+                    extentHeight = 0;
+                    double top = 0;
+
+                    for (int i = 0; i < childCount; i++)
+                    {
+                        extentHeight += targetHeight;
+                        heights[i] = targetHeight;
+                        _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]);
+                        top += heights[i];
+
+                        FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                        if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i]));
+                    }
+                    _extent = new Size(maxChildWidthOrHeight, extentHeight);
+
+                    flag = true;
+
+                    CanScrollLeftOrUp = false;
+                    CanScrollRightOrDown = false;
+                }
+            }
+
+            // 3) we can not fit all the children in the viewport, so now we will enable scrolling/virtualizing items
+            if (!flag)
+            {
+                _maxVisibleItems = (int)Math.Floor(_viewPort.Height / MinimumChildHeight);          // calculate how many visible children we can show at once
+                double targetHeight = availableSize.Height / _maxVisibleItems;                      // calculate the new target width
+                FirstVisibleIndex = _firstVisibleIndex;
+
+                extentHeight = 0;
+                double top = 0;
+                for (int i = 0; i < childCount; i++)
+                {
+                    extentHeight += targetHeight;
+                    heights[i] = targetHeight;
+                    _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]);
+                    top += heights[i];
+
+                    FrameworkElement child = InternalChildren[i] as FrameworkElement;
+                    if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i]));
+                }
+                _extent = new Size(maxChildWidthOrHeight, extentHeight);
+
+                CanScrollLeftOrUp = LastVisibleIndex < childCount - 1;
+                CanScrollRightOrDown = FirstVisibleIndex > 0;
+            }
+
+            return new Size(maxChildWidthOrHeight, double.IsInfinity(availableSize.Height) ? _extent.Height : availableSize.Height);
+
+        }
+        #endregion
+
+        #region ArrangeOverride
+
+        /// <summary>
+        /// Arrange Override
+        /// </summary>
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            // monitors changes to the ScrollViewer extent value
+            if (_oldExtent != _extent)
+            {
+                _oldExtent = _extent;
+                if (_scrollOwner != null)
+                    _scrollOwner.InvalidateScrollInfo();
+            }
+
+            // monitors changes to the parent container size, (ie window resizes)
+            if (finalSize != _lastSize)
+            {
+                _lastSize = finalSize;
+                if (_scrollOwner != null)
+                    _scrollOwner.InvalidateScrollInfo();
+            }
+
+            // monitor scrolling being removed
+            bool invalidateMeasure = false;
+            if (_extent.Width <= _viewPort.Width && _offset.X > 0)
+            {
+                _offset.X = 0;
+                _translateTransform.X = 0;
+
+                if (_scrollOwner != null)
+                    _scrollOwner.InvalidateScrollInfo();
+
+                invalidateMeasure = true;
+            }
+            if (_extent.Height <= _viewPort.Height && _offset.Y > 0)
+            {
+                _offset.Y = 0;
+                _translateTransform.Y = 0;
+
+                if (_scrollOwner != null)
+                    _scrollOwner.InvalidateScrollInfo();
+
+                invalidateMeasure = true;
+            }
+            if (invalidateMeasure)
+                InvalidateMeasure();
+
+
+
+            // arrange the children
+            for (var i = 0; i < InternalChildren.Count; i++)
+            {
+                InternalChildren[i].Arrange(_childRects[i]);
+            }
+
+            // we need these lines as when the Scroll Buttons get Shown/Hidden,
+            // the _offset value gets out of line, this will ensure that our scroll position stays in line
+            if (InternalChildren.Count > 0)
+            {
+                _offset = _childRects[FirstVisibleIndex].TopLeft;
+                _translateTransform.X = -_offset.X;
+                _translateTransform.Y = -_offset.Y;
+            }
+
+            return finalSize;
+        }
+        #endregion
+
+        private void EnsureChildRects()
+        {
+            while (InternalChildren.Count > _childRects.Count)
+                _childRects.Add(new Rect());
+        }
+
+        #region IScrollInfo Members
+
+        public bool CanHorizontallyScroll { get; set; }
+
+        public bool CanVerticallyScroll { get; set; }
+
+        public double ExtentHeight
+        {
+            get { return _extent.Height; }
+        }
+
+        public double ExtentWidth
+        {
+            get { return _extent.Width; }
+        }
+
+        public double HorizontalOffset
+        {
+            get { return _offset.X; }
+        }
+
+        public void LineDown()
+        {
+            LineRight();
+        }
+
+        public void LineLeft()
+        {
+            // this works because we can guarantee that when we are in scroll mode, 
+            // there will be children, and they will all be of equal size
+            FirstVisibleIndex++;
+
+            if (_tabStripPlacement == Dock.Top || _tabStripPlacement == Dock.Bottom)
+                SetHorizontalOffset(HorizontalOffset + _childRects[0].Width);
+            else
+                SetVerticalOffset(HorizontalOffset + _childRects[0].Height);
+        }
+
+        public void LineRight()
+        {
+            FirstVisibleIndex--;
+
+            if (_tabStripPlacement == Dock.Top || _tabStripPlacement == Dock.Bottom)
+                SetHorizontalOffset(HorizontalOffset - _childRects[0].Width);
+            else
+                SetVerticalOffset(HorizontalOffset - _childRects[0].Height);
+        }
+
+        public void LineUp()
+        {
+            LineLeft();
+        }
+
+        public Rect MakeVisible(Visual visual, Rect rectangle)
+        {
+            InvalidateMeasure();
+            UpdateLayout();
+
+            TabControl ic = ItemsControl.GetItemsOwner(this) as TabControl;
+            if (ic == null) return Rect.Empty;
+
+            int index = -1;
+            var tabsCount = ic.GetTabsCount();
+            for (int i = 0; i < tabsCount; i++)
+            {
+                if (visual.Equals(ic.GetTabItem(i)))
+                {
+                    index = i;
+                    break;
+                }
+            }
+            if (index > -1)
+            {
+                if (index < FirstVisibleIndex)
+                    FirstVisibleIndex = index;
+                else if (index > LastVisibleIndex)
+                {
+                    while (index > LastVisibleIndex)
+                        FirstVisibleIndex++;
+                }
+
+                InvalidateArrange();
+            }
+
+            return Rect.Empty;
+        }
+
+        public void MouseWheelDown()
+        {
+            LineDown();
+        }
+
+        public void MouseWheelLeft()
+        {
+            LineLeft();
+        }
+
+        public void MouseWheelRight()
+        {
+            LineRight();
+        }
+
+        public void MouseWheelUp()
+        {
+            LineUp();
+        }
+
+        public void PageDown()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void PageLeft()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void PageRight()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void PageUp()
+        {
+            throw new NotImplementedException();
+        }
+
+        public ScrollViewer ScrollOwner
+        {
+            get { return _scrollOwner; }
+            set { _scrollOwner = value; }
+        }
+
+        public void SetHorizontalOffset(double offset)
+        {
+            if (offset < 0 || _viewPort.Width >= _extent.Width)
+                offset = 0;
+            else
+            {
+                if (offset + _viewPort.Width > _extent.Width)
+                    offset = _extent.Width - _viewPort.Width;
+            }
+
+            _offset.X = offset;
+            if (_scrollOwner != null)
+                _scrollOwner.InvalidateScrollInfo();
+
+            _translateTransform.X = -offset;
+
+            InvalidateMeasure();
+        }
+
+        public void SetVerticalOffset(double offset)
+        {
+            if (offset < 0 || _viewPort.Height >= _extent.Height)
+                offset = 0;
+            else
+            {
+                if (offset + _viewPort.Height > _extent.Height)
+                    offset = _extent.Height - _viewPort.Height;
+            }
+
+            _offset.Y = offset;
+            if (_scrollOwner != null)
+                _scrollOwner.InvalidateScrollInfo();
+
+            _translateTransform.Y = -offset;
+
+            InvalidateMeasure();
+        }
+
+        public double VerticalOffset
+        {
+            get { return _offset.Y; }
+        }
+
+        public double ViewportHeight
+        {
+            get { return _viewPort.Height; }
+        }
+
+        public double ViewportWidth
+        {
+            get { return _viewPort.Width; }
+        }
+
+        #endregion
+
+        #region Helpers
+
+        private static void SetDimensions(TabItem tabItem)
+        {
+            if (tabItem.Dimension == null)
+            {
+                // store the original size specifications of the tab
+                tabItem.Dimension =
+                    new Dimension
+                    {
+                        Height = tabItem.Height,
+                        Width = tabItem.Width,
+                        MaxHeight = tabItem.MaxHeight,
+                        MaxWidth = tabItem.MaxWidth,
+                        MinHeight = tabItem.MinHeight,
+                        MinWidth = tabItem.MinWidth
+                    };
+            }
+            else
+            {
+                // restore the original values for the tab
+                tabItem.BeginInit();
+                tabItem.Height = tabItem.Dimension.Height;
+                tabItem.Width = tabItem.Dimension.Width;
+                tabItem.MaxHeight = tabItem.Dimension.MaxHeight;
+                tabItem.MaxWidth = tabItem.Dimension.MaxWidth;
+                tabItem.MinHeight = tabItem.Dimension.MinHeight;
+                tabItem.MinWidth = tabItem.Dimension.MinWidth;
+                tabItem.EndInit();
+            }
+        }
+
+        private static void ClearDimensions(FrameworkElement tabItem)
+        {
+            // remove any size restrictions from the Header,
+            // this is because the TabControl's size restriction properties takes precedence over
+            // the individual tab items
+            // eg, if the TabControl sets the TabItemMaxWidth property to 300, but the Header
+            // has a minWidth of 400, the TabControls value of 300 should be used
+            tabItem.BeginInit();
+            tabItem.Height = double.NaN;
+            tabItem.Width = double.NaN;
+            tabItem.MaxHeight = double.PositiveInfinity;
+            tabItem.MaxWidth = double.PositiveInfinity;
+            tabItem.MinHeight = 0;
+            tabItem.MinWidth = 0;
+            tabItem.EndInit();
+        }
+
+        #endregion
+    }
+}

+ 1 - 1
src/YSAI.Netty/YSAI.Netty.csproj

@@ -3,7 +3,7 @@
     <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
-    <Version>23.338.17729</Version>
+    <Version>23.339.30696</Version>
     <PackageOutputPath Condition="'$(Configuration)' == 'Release'">../YSAI.Publish/Release</PackageOutputPath>
     <PackageOutputPath Condition="'$(Configuration)' == 'Debug'">../YSAI.Publish/Debug</PackageOutputPath>
     <Authors>Shun</Authors>

+ 38 - 0
src/YSAI.Netty/client/NettyClientHandler.cs

@@ -0,0 +1,38 @@
+using DotNetty.Transport.Channels;
+
+namespace YSAI.Netty.client
+{
+    public class NettyClientHandler : ChannelHandlerAdapter
+    {
+        NettyClientOperate nettyClientOperate;
+        public NettyClientHandler(NettyClientOperate nettyClientOperate)
+        {
+            this.nettyClientOperate = nettyClientOperate;
+        }
+        /// <summary>
+        /// 通道读取
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="message">消息</param>
+        public override void ChannelRead(IChannelHandlerContext context, object message)
+        {
+            nettyClientOperate.Response(context, message);
+        }
+        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
+        /// <summary>
+        /// 通道异常
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="exception"></param>
+        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
+        {
+            nettyClientOperate.Exception(context, exception);
+        }
+    }
+}
+
+
+
+
+
+

+ 61 - 91
src/YSAI.Netty/client/NettyClientOperate.cs

@@ -1,4 +1,5 @@
-using DotNetty.Codecs;
+using DotNetty.Buffers;
+using DotNetty.Codecs;
 using DotNetty.Handlers.Tls;
 using DotNetty.Transport.Bootstrapping;
 using DotNetty.Transport.Channels;
@@ -7,6 +8,7 @@ using System.Collections.Concurrent;
 using System.Net;
 using System.Net.Security;
 using System.Security.Cryptography.X509Certificates;
+using System.Text;
 using YSAI.Model.data;
 using YSAI.Model.@enum;
 using YSAI.Model.@interface;
@@ -195,7 +197,21 @@ namespace YSAI.Netty.client
             /// </summary>
             public string C { get; set; }
         }
+        /// <summary>
+        /// 信息
+        /// </summary>
+        public class Info
+        {
+            /// <summary>
+            /// 通道处理程序上下文
+            /// </summary>
+            public IChannelHandlerContext Context { get; set; }
 
+            /// <summary>
+            /// 信息
+            /// </summary>
+            public string Msg { get; set; }
+        }
         /// <summary>
         /// 任务处理
         /// </summary>
@@ -228,12 +244,10 @@ namespace YSAI.Netty.client
 
                                         if (Content.IsJson())
                                         {
-                                            //OnEventHandler(this, new EventResult(true, $"{TAG} 接收到主题 ( {Topic} ) 内容 ( {Content} )", "{" + $"\"Topic\":\"{Topic}\",\"Content\":{Content},\"Terminal\":\"{Terminal}\"" + "}", ResultType.Dynamic));
                                             OnEventHandler(this, new EventResult(true, $"{TAG} 接收到主题 ( {Topic} ) 内容 ( {Content} )", "{" + $"\"Topic\":\"{Topic}\",\"Content\":{Content}" + "}", ResultType.Dynamic));
                                         }
                                         else
                                         {
-                                            //OnEventHandler(this, new EventResult(true, $"{TAG} 接收到主题 ( {Topic} ) 内容 ( {Content} )", "{" + $"\"Topic\":\"{Topic}\",\"Content\":\"{Content}\",\"Terminal\":\"{Terminal}\"" + "}", ResultType.Dynamic));
                                             OnEventHandler(this, new EventResult(true, $"{TAG} 接收到主题 ( {Topic} ) 内容 ( {Content} )", "{" + $"\"Topic\":\"{Topic}\",\"Content\":\"{Content}\"" + "}", ResultType.Dynamic));
                                         }
                                     }
@@ -247,90 +261,6 @@ namespace YSAI.Netty.client
             }, token.Token);
         }
 
-        /// <summary>
-        /// 内部消息接收
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void OnMe(object? sender, EventResult e)
-        {
-            if (e.State)
-            {
-                //收到数据
-                if (SubIoc.Count > 0)
-                {
-                    DataQueue.Enqueue(e.RData as Info);
-                }
-            }
-            else
-            {
-                //出现异常
-                OnEventHandler(this, new EventResult(false, $"订阅异常:{e.Message}"));
-                Off();
-            }
-        }
-
-        /// <summary>
-        /// 信息
-        /// </summary>
-        public class Info
-        {
-            /// <summary>
-            /// 通道处理程序上下文
-            /// </summary>
-            public IChannelHandlerContext Context { get; set; }
-
-            /// <summary>
-            /// 信息
-            /// </summary>
-            public string Msg { get; set; }
-        }
-
-        /// <summary>
-        /// 消息接收处理
-        /// </summary>
-        public class SecureChatClientHandler : SimpleChannelInboundHandler<string>
-        {
-            /// <summary>
-            /// 消息事件
-            /// </summary>
-            private Action<object?, EventResult> MessageEvent;
-
-            /// <summary>
-            /// 构造函数
-            /// </summary>
-            /// <param name="MessageEvent">息事件</param>
-            public SecureChatClientHandler(Action<object?, EventResult> MessageEvent)
-            {
-                this.MessageEvent = MessageEvent;
-            }
-
-            /// <summary>
-            /// 通道消息
-            /// </summary>
-            /// <param name="contex">通道处理程序上下文</param>
-            /// <param name="msg">消息</param>
-            protected override void ChannelRead0(IChannelHandlerContext contex, string msg)
-            {
-                //消息抛出
-                MessageEvent?.Invoke(this, new EventResult(true, msg, new Info { Context = contex, Msg = msg }, ResultType.Dynamic));
-            }
-
-            /// <summary>
-            /// 出现了异常
-            /// </summary>
-            /// <param name="contex">通道处理程序上下文</param>
-            /// <param name="e">异常信息</param>
-            public override void ExceptionCaught(IChannelHandlerContext contex, Exception ex)
-            {
-                //消息抛出
-                MessageEvent?.Invoke(this, new EventResult(false, ex.Message));
-
-                //通道关闭
-                contex.CloseAsync();
-            }
-        }
-
         public OperateResult On()
         {
             string SN = Depart("On");
@@ -367,9 +297,9 @@ namespace YSAI.Netty.client
                     {
                         pipeline.AddLast(new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(CertTargetHost)));
                     }
-                    pipeline.AddLast(new DelimiterBasedFrameDecoder(8192, Delimiters.LineDelimiter()));
-                    //这里就是启动订阅
-                    pipeline.AddLast(new StringEncoder(), new StringDecoder(), new SecureChatClientHandler(OnMe));
+                    pipeline.AddLast("framing-enc", new LengthFieldPrepender(8));
+                    pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 8, 0, 8));
+                    pipeline.AddLast(new NettyClientHandler(this));
                 }));
 
                 //连接
@@ -409,7 +339,7 @@ namespace YSAI.Netty.client
                     msg = "{" + string.Format("\"T\":\"{0}\",\"C\":\"{1}\"", Topic, Content) + "}\r\n";
                 }
                 //等待执行完成
-                Channel.WriteAndFlushAsync(msg).Wait();
+                Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(msg))).Wait();
                 return Break(SN, true);
             }
             catch (Exception ex)
@@ -568,5 +498,45 @@ namespace YSAI.Netty.client
         {
             return Task.Run(() => CreateInstance(Basics));
         }
+
+        /// <summary>
+        /// 响应,内部使用
+        /// </summary>
+        public void Response(IChannelHandlerContext context, object message)
+        {
+            DataQueue.Enqueue(new Info { Context = context, Msg = (message as IByteBuffer).ToString(Encoding.UTF8) });
+        }
+        /// <summary>
+        /// 异常,内部使用
+        /// </summary>
+        public void Exception(IChannelHandlerContext context, Exception exception)
+        {
+            //消息抛出
+            OnEventHandler(this, new EventResult(false, $"{IpHandle(context.Channel.RemoteAddress)} {exception.Message}"));
+
+            //通道关闭
+            context.CloseAsync();
+
+            //关闭
+            Off();
+        }
+
+        /// <summary>
+        /// IP处理
+        /// </summary>
+        /// <param name="Ip"></param>
+        /// <returns></returns>
+        private string IpHandle(EndPoint Ip)
+        {
+            //原始数据
+            string ip = string.Empty;
+            string? ip_od = Ip.ToString();
+            if (!string.IsNullOrWhiteSpace(ip_od))
+            {
+                string[] str = ip_od.Replace("[", "").Replace("]", "").Split(':');
+                ip = $"{str[3]}:{str[4]}";
+            }
+            return ip;
+        }
     }
 }

+ 63 - 0
src/YSAI.Netty/service/NettyServiceHandler.cs

@@ -0,0 +1,63 @@
+using DotNetty.Transport.Channels;
+using DotNetty.Transport.Channels.Groups;
+
+namespace YSAI.Netty.service
+{
+    /// <summary>
+    /// netty 服务端处理
+    /// </summary>
+    public class NettyServiceHandler : ChannelHandlerAdapter
+    {
+        public NettyServiceHandler(NettyServiceOperate rPCServer)
+        {
+            nettyServiceOperate = rPCServer;
+        }
+
+        NettyServiceOperate nettyServiceOperate { get; }
+
+        /// <summary>
+        /// 通道激活
+        /// </summary>
+        /// <param name="contex"></param>
+        public override void ChannelActive(IChannelHandlerContext context)
+        {
+            nettyServiceOperate.Active(context);
+        }
+        /// <summary>
+        /// 通道读取
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="message">消息</param>
+        public override void ChannelRead(IChannelHandlerContext context, object message)
+        {
+            nettyServiceOperate.Response(context, message);
+        }
+        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
+        /// <summary>
+        /// 通道异常
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="exception"></param>
+        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
+        {
+            nettyServiceOperate.Exception(context, exception);
+        }
+    }
+
+    /// <summary>
+    /// 群发送至连上服务器的所有客户端,过滤掉发送端
+    /// </summary>
+    public class NettyServiceChannelMatcherHandler : IChannelMatcher
+    {
+        readonly IChannelId id;
+        /// <summary>
+        /// 群发送至连上服务器的所有客户端,过滤掉发送端
+        /// </summary>
+        /// <param name="id"></param>
+        public NettyServiceChannelMatcherHandler(IChannelId id)
+        {
+            this.id = id;
+        }
+        public bool Matches(IChannel channel) => channel.Id != this.id;
+    }
+}

+ 113 - 194
src/YSAI.Netty/service/NettyServiceOperate.cs

@@ -1,4 +1,5 @@
-using DotNetty.Codecs;
+using DotNetty.Buffers;
+using DotNetty.Codecs;
 using DotNetty.Handlers.Logging;
 using DotNetty.Handlers.Tls;
 using DotNetty.Transport.Bootstrapping;
@@ -8,6 +9,7 @@ using DotNetty.Transport.Channels.Sockets;
 using System.Collections.Concurrent;
 using System.Net;
 using System.Security.Cryptography.X509Certificates;
+using System.Text;
 using YSAI.Model.data;
 using YSAI.Model.@interface;
 using YSAI.Unility;
@@ -91,186 +93,6 @@ namespace YSAI.Netty.service
             this.basics = basics;
         }
 
-        /// <summary>
-        /// 内部消息接收
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        protected void OnMe(object? sender, EventResult e)
-        {
-            OnEventHandler(this, e);
-            if (!e.State)
-            {
-                Off();
-            }
-        }
-
-        /// <summary>
-        /// 处理程序
-        /// </summary>
-        public class SecureChatServerHandler : SimpleChannelInboundHandler<string>
-        {
-            /// <summary>
-            /// IP处理
-            /// </summary>
-            /// <param name="Ip"></param>
-            /// <returns></returns>
-            private string IpHandle(EndPoint Ip)
-            {
-                //原始数据
-                string ip = string.Empty;
-                string? ip_od = Ip.ToString();
-                if (!string.IsNullOrWhiteSpace(ip_od))
-                {
-                    string[] str = ip_od.Replace("[", "").Replace("]", "").Split(':');
-                    ip = $"{str[3]}:{str[4]}";
-                }
-                return ip;
-            }
-
-            /// <summary>
-            /// 客户端容器
-            /// </summary>
-            private static volatile ConcurrentDictionary<string, IChannel> ClientIoc = new ConcurrentDictionary<string, IChannel>();
-
-            /// <summary>
-            /// 消息事件
-            /// </summary>
-            private Action<object?, EventResult> MessageEvent;
-
-            /// <summary>
-            /// 组
-            /// </summary>
-            private static volatile IChannelGroup ChannelGroup;
-
-            /// <summary>
-            /// 构造函数
-            /// </summary>
-            /// <param name="MessageEvent">消息事件</param>
-            public SecureChatServerHandler(Action<object?, EventResult> MessageEvent)
-            {
-                this.MessageEvent = MessageEvent;
-            }
-
-            /// <summary>
-            /// 通道激活
-            /// </summary>
-            /// <param name="contex"></param>
-            public override void ChannelActive(IChannelHandlerContext contex)
-            {
-                IChannelGroup g = ChannelGroup;
-                if (g == null)
-                {
-                    lock (this)
-                    {
-                        if (ChannelGroup == null)
-                        {
-                            g = ChannelGroup = new DefaultChannelGroup(contex.Executor);
-                        }
-                    }
-                }
-                string ip = IpHandle(contex.Channel.RemoteAddress);
-                ClientIoc.AddOrUpdate(ip, contex.Channel, (k, v) => contex.Channel);
-                MessageEvent?.Invoke(this, new EventResult(true, $"{ip} 激活了通道", ip));
-            }
-
-            /// <summary>
-            /// 群发送至连上服务器的所有客户端,过滤掉发送端
-            /// </summary>
-            //class EveryOneBut : IChannelMatcher
-            //{
-            //    readonly IChannelId id;
-            //    public EveryOneBut(IChannelId id)
-            //    {
-            //        this.id = id;
-            //    }
-            //    public bool Matches(IChannel channel) => channel.Id != this.id;
-            //}
-
-            protected override void ChannelRead0(IChannelHandlerContext contex, string msg)
-            {
-                //群发
-                ChannelGroup.WriteAndFlushAsync(string.Format("{0}\n", msg));
-
-                //群发过滤发送端
-                //group.WriteAndFlushAsync(string.Format("{0}\n", msg),new EveryOneBut(contex.Channel.Id));
-
-                //给发送端回显数据
-                //contex.WriteAndFlushAsync(string.Format("{0}\n", msg));
-            }
-
-            public override void ChannelReadComplete(IChannelHandlerContext ctx) => ctx.Flush();
-
-            /// <summary>
-            /// 出现了异常
-            /// </summary>
-            /// <param name="contex">通道处理程序上下文</param>
-            /// <param name="e">异常信息</param>
-            public override void ExceptionCaught(IChannelHandlerContext contex, Exception ex)
-            {
-                //移除IP
-                string ip = IpHandle(contex.Channel.RemoteAddress);
-                ClientIoc.Remove(ip, out _);
-
-                //消息抛出
-                MessageEvent?.Invoke(this, new EventResult(false, $"{contex.Channel.RemoteAddress} {ex.Message}", ip));
-
-                //通道关闭
-                contex.CloseAsync();
-            }
-
-            /// <summary>
-            /// 数据发送
-            /// </summary>
-            /// <param name="Data">数据</param>
-            /// <param name="IpPort">IP端口</param>
-            /// <returns></returns>
-            public static OperateResult Send(string Data, string[]? IpPort = null)
-            {
-                try
-                {
-                    if (ChannelGroup != null)
-                    {
-                        if (IpPort != null)
-                        {
-                            List<string> FailMessage = new List<string>();
-                            foreach (var ip in IpPort)
-                            {
-                                if (ClientIoc.ContainsKey(ip))
-                                {
-                                    ClientIoc[ip].WriteAndFlushAsync(string.Format("{0}\n", Data));
-                                }
-                                else
-                                {
-                                    FailMessage.Add($"{IpPort} 终端不存在");
-                                }
-                            }
-                            if (FailMessage.Count > 0)
-                            {
-                                return new OperateResult(false, FailMessage.ToJson(), IpPort.Length);
-                            }
-                            return new OperateResult(true, string.Empty, IpPort.Length);
-                        }
-                        else
-                        {
-                            //群发
-                            ChannelGroup.WriteAndFlushAsync(string.Format("{0}\n", Data));
-                            return new OperateResult(true, string.Empty, 1);
-                        }
-                    }
-                    else
-                    {
-                        return new OperateResult(false, "当前没有客户端连接", 1);
-                    }
-                }
-                catch (Exception ex)
-                {
-                    return new OperateResult(false, ex.Message, 1);
-                }
-            }
-
-            public override bool IsSharable => true;
-        }
 
         /// <summary>
         /// 释放
@@ -314,12 +136,6 @@ namespace YSAI.Netty.service
                 ClientGroup = new MultithreadEventLoopGroup(1);
                 ClientMessageGroup = new MultithreadEventLoopGroup();
 
-                //实例化编码格式
-                var STRING_ENCODER = new StringEncoder();
-                var STRING_DECODER = new StringDecoder();
-                //实例化处理程序
-                var SERVER_HANDLER = new SecureChatServerHandler(OnMe);
-
                 //走TCP通道
                 Communication
                 .Group(ClientGroup, ClientMessageGroup)  //创建一个组
@@ -333,8 +149,9 @@ namespace YSAI.Netty.service
                     {
                         pipeline.AddLast(TlsHandler.Server(Cert));
                     }
-                    pipeline.AddLast(new DelimiterBasedFrameDecoder(8192, Delimiters.LineDelimiter()));
-                    pipeline.AddLast(STRING_ENCODER, STRING_DECODER, SERVER_HANDLER);
+                    pipeline.AddLast("framing-enc", new LengthFieldPrepender(8));
+                    pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 8, 0, 8));
+                    pipeline.AddLast(new NettyServiceHandler(this));
                 }));
 
                 //创建
@@ -399,7 +216,7 @@ namespace YSAI.Netty.service
         /// <param name="Data">数据</param>
         /// <param name="IpPort">IP端口</param>
         /// <returns></returns>
-        public OperateResult Send(string Data, string[]? IpPort = null)
+        public OperateResult Send(byte[] Data, string[]? IpPort = null)
         {
             string SN = Depart("Send");
             try
@@ -408,8 +225,39 @@ namespace YSAI.Netty.service
                 {
                     return Break(SN, false, "未启动");
                 }
-                //等待发送完成
-                return Break(SN, SecureChatServerHandler.Send(Data, IpPort));
+                if (ChannelGroup != null)
+                {
+                    if (IpPort != null)
+                    {
+                        List<string> FailMessage = new List<string>();
+                        foreach (var ip in IpPort)
+                        {
+                            if (ClientIoc.ContainsKey(ip))
+                            {
+                                ClientIoc[ip].WriteAndFlushAsync(Unpooled.WrappedBuffer(Data));
+                            }
+                            else
+                            {
+                                FailMessage.Add($"{IpPort} 终端不存在");
+                            }
+                        }
+                        if (FailMessage.Count > 0)
+                        {
+                            return Break(SN, false, FailMessage.ToJson());
+                        }
+                        return Break(SN, true);
+                    }
+                    else
+                    {
+                        //群发
+                        ChannelGroup.WriteAndFlushAsync(Unpooled.WrappedBuffer(Data));
+                        return Break(SN, true);
+                    }
+                }
+                else
+                {
+                    return Break(SN, false, "当前没有客户端连接");
+                }
             }
             catch (Exception ex)
             {
@@ -423,7 +271,7 @@ namespace YSAI.Netty.service
         /// <param name="Data">数据</param>
         /// <param name="IpPort">IP端口</param>
         /// <returns></returns>
-        public Task<OperateResult> SendAsync(string Data, string[]? IpPort = null)
+        public Task<OperateResult> SendAsync(byte[] Data, string[]? IpPort = null)
         {
             return Task.Run(() => Send(Data, IpPort));
         }
@@ -457,7 +305,7 @@ namespace YSAI.Netty.service
                 }
 
                 //等待发送完成
-                return Break(SN, SecureChatServerHandler.Send(msg, IpPort));
+                return Break(SN, Send(Encoding.UTF8.GetBytes(msg), IpPort));
             }
             catch (Exception ex)
             {
@@ -476,5 +324,76 @@ namespace YSAI.Netty.service
         {
             return Task.Run(() => Send(Topic, Content, IpPort));
         }
+
+        /// <summary>
+        /// 客户端容器
+        /// </summary>
+        private static volatile ConcurrentDictionary<string, IChannel> ClientIoc = new ConcurrentDictionary<string, IChannel>();
+        /// <summary>
+        /// 组
+        /// </summary>
+        private static volatile IChannelGroup ChannelGroup;
+
+
+        /// <summary>
+        /// 激活,内部使用
+        /// </summary>
+        /// <param name="context"></param>
+        public void Active(IChannelHandlerContext context)
+        {
+            if (ChannelGroup == null)
+            {
+                ChannelGroup = new DefaultChannelGroup(context.Executor);
+            }
+            ChannelGroup.Add(context.Channel);
+
+            string ip = IpHandle(context.Channel.RemoteAddress);
+            ClientIoc.AddOrUpdate(ip, context.Channel, (k, v) => context.Channel);
+            OnEventHandler(this, new EventResult(true, $"{ip} 激活了通道", ip));
+        }
+
+        /// <summary>
+        /// 响应,内部使用
+        /// </summary>
+        public void Response(IChannelHandlerContext context, object message)
+        {
+            //群发过滤发送端
+            ChannelGroup.WriteAndFlushAsync(message);//, new NettyServiceChannelMatcherHandler(context.Channel.Id));
+        }
+        /// <summary>
+        /// 异常,内部使用
+        /// </summary>
+        public void Exception(IChannelHandlerContext context, Exception exception)
+        {
+            ChannelGroup.Remove(context.Channel);
+
+            //移除IP
+            string ip = IpHandle(context.Channel.RemoteAddress);
+            ClientIoc.Remove(ip, out _);
+
+            //消息抛出
+            OnEventHandler(this, new EventResult(false, $"{context.Channel.RemoteAddress} {exception.Message}", ip));
+
+            //通道关闭
+            context.CloseAsync();
+        }
+
+        /// <summary>
+        /// IP处理
+        /// </summary>
+        /// <param name="Ip"></param>
+        /// <returns></returns>
+        private string IpHandle(EndPoint Ip)
+        {
+            //原始数据
+            string ip = string.Empty;
+            string? ip_od = Ip.ToString();
+            if (!string.IsNullOrWhiteSpace(ip_od))
+            {
+                string[] str = ip_od.Replace("[", "").Replace("]", "").Split(':');
+                ip = $"{str[3]}:{str[4]}";
+            }
+            return ip;
+        }
     }
 }

+ 77 - 1
src/YSAI.Tests/Program.cs

@@ -1,4 +1,80 @@
-Console.WriteLine();
+using YSAI.Log;
+using YSAI.Model.data;
+using YSAI.Netty.client;
+using YSAI.Netty.service;
+using YSAI.Unility;
+
+NettyServiceOperate nettyServiceOperate = NettyServiceOperate.Instance(new NettyServiceData.Basics
+{
+    Port = 8007,
+    SN = "1"
+});
+
+Console.WriteLine(nettyServiceOperate.On().ToJson());
+nettyServiceOperate.OnEvent += delegate (object? sender, EventResult e)
+{
+    LogHelper.Info(e.ToJson().JsonFormatting());
+};
+
+//转发协议
+NettyClientOperate operate1 = NettyClientOperate.Instance(new NettyClientData.Basics
+{
+    Host = "127.0.0.1",
+    Port = 8007,
+    SN = "1"
+});
+//打开
+OperateResult result = operate1.On();
+LogHelper.Info(result.ToJson().JsonFormatting());
+//消费
+operate1.OnEvent += delegate (object? sender, EventResult e)
+{
+    LogHelper.Error("客户端1\r\n" + e.ToJson().JsonFormatting());
+};
+result = operate1.Subscribe("测试1");
+LogHelper.Info(result.ToJson().JsonFormatting());
+
+
+
+////转发协议
+//NettyClientOperate operate2 = NettyClientOperate.Instance(new NettyClientData.Basics
+//{
+//    Host = "127.0.0.1",
+//    Port = 8007,
+//    SN = "2"
+//});
+////打开
+//result = operate2.On();
+//LogHelper.Info(result.ToJson().JsonFormatting());
+////消费
+//operate2.OnEvent += delegate (object? sender, EventResult e)
+//{
+//    LogHelper.Error("客户端2\r\n" + e.ToJson().JsonFormatting());
+//};
+//result = operate2.Subscribe("测试");
+//LogHelper.Info(result.ToJson().JsonFormatting());
+
+while (true)
+{
+    for (int i = 0; i < 9999999999; i++)
+    {
+        Console.ReadLine();
+        if (i % 2 == 0)
+        {
+            //生产
+            Console.WriteLine(operate1.Produce("测试1", "客户端1 -> 客户端2  " + new Random().NextDouble().ToString()).ToJson().JsonFormatting());
+        }
+        else
+        {
+            //Console.WriteLine(operate2.Produce("测试", "客户端2 -> 客户端1  " + new Random().NextDouble().ToString()).ToJson().JsonFormatting());
+        }
+    }
+}
+
+
+
+
+//Console.WriteLine();
 
 //using System.Collections.Concurrent;
 //using YSAI.Core.reflection;

+ 1 - 1
src/YSAI.Ver.Manage.Tool/Program.cs

@@ -19,7 +19,7 @@ List<string> strings = new List<string>
                 //"YSAI.Kafka",
                 //"YSAI.Mqtt",
                 //"YSAI.NetMQ",
-                //"YSAI.Netty",
+                "YSAI.Netty",
                 //"YSAI.RabbitMQ",
                 //////////"YSAI.Rest",