WPF 自定义TabControl控件样式

2022-07-22,,,,

一、前言

程序中经常会用到tabcontrol控件,默认的控件样式很普通。而且样式或功能不一定符合我们的要求。比如:我们需要tabcontrol的标题能够居中、或平均分布;或者我们希望tabcontrol的标题能够进行关闭。要实现这些功能我们需要对tabcontrol的样式进行定义。

二、实现tabcontrol的标题平均分布

默认的tabcontrol标题是使用tabpanel容器包含的。要想实现tabcontrol标题头平均分布,需要把tabpanel替换成uniformgrid;

替换后的tabcontrol样式如下:

 <style x:key="tabcontrolstyle" targettype="{x:type tabcontrol}">
            <setter property="padding" value="2"/>
            <setter property="horizontalcontentalignment" value="center"/>
            <setter property="verticalcontentalignment" value="center"/>
            <setter property="background" value="white"/>
            <setter property="borderbrush" value="#ffacacac"/>
            <setter property="borderthickness" value="1"/>
            <setter property="foreground" value="{dynamicresource {x:static systemcolors.controltextbrushkey}}"/>
            <setter property="template">
                <setter.value>
                    <controltemplate targettype="{x:type tabcontrol}">
                        <grid x:name="templateroot" cliptobounds="true" snapstodevicepixels="true" keyboardnavigation.tabnavigation="local">
                            <grid.columndefinitions>
                                <columndefinition x:name="columndefinition0"/>
                                <columndefinition x:name="columndefinition1" width="0"/>
                            </grid.columndefinitions>
                            <grid.rowdefinitions>
                                <rowdefinition x:name="rowdefinition0" height="auto"/>
                                <rowdefinition x:name="rowdefinition1" height="*"/>
                            </grid.rowdefinitions>
                            <uniformgrid x:name="headerpanel" rows="1" background="transparent" grid.column="0" isitemshost="true" margin="0" grid.row="0" keyboardnavigation.tabindex="1" panel.zindex="1"/>
                            <line x1="0" x2="{binding actualwidth, relativesource={relativesource self}}" stroke="white" strokethickness="0.1" verticalalignment="bottom" margin="0 0 0 1" snapstodevicepixels="true"/>
                            <border x:name="contentpanel" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}" grid.column="0" keyboardnavigation.directionalnavigation="contained" grid.row="1" keyboardnavigation.tabindex="2" keyboardnavigation.tabnavigation="local">
                                <contentpresenter x:name="part_selectedcontenthost" contenttemplate="{templatebinding selectedcontenttemplate}" content="{templatebinding selectedcontent}" contentstringformat="{templatebinding selectedcontentstringformat}" contentsource="selectedcontent" margin="0" snapstodevicepixels="{templatebinding snapstodevicepixels}"/>
                            </border>
                        </grid>
                        <controltemplate.triggers>
                            <trigger property="tabstripplacement" value="bottom">
                                <setter property="grid.row" targetname="headerpanel" value="1"/>
                                <setter property="grid.row" targetname="contentpanel" value="0"/>
                                <setter property="height" targetname="rowdefinition0" value="*"/>
                                <setter property="height" targetname="rowdefinition1" value="auto"/>
                            </trigger>
                            <trigger property="tabstripplacement" value="left">
                                <setter property="grid.row" targetname="headerpanel" value="0"/>
                                <setter property="grid.row" targetname="contentpanel" value="0"/>
                                <setter property="grid.column" targetname="headerpanel" value="0"/>
                                <setter property="grid.column" targetname="contentpanel" value="1"/>
                                <setter property="width" targetname="columndefinition0" value="auto"/>
                                <setter property="width" targetname="columndefinition1" value="*"/>
                                <setter property="height" targetname="rowdefinition0" value="*"/>
                                <setter property="height" targetname="rowdefinition1" value="0"/>
                            </trigger>
                            <trigger property="tabstripplacement" value="right">
                                <setter property="grid.row" targetname="headerpanel" value="0"/>
                                <setter property="grid.row" targetname="contentpanel" value="0"/>
                                <setter property="grid.column" targetname="headerpanel" value="1"/>
                                <setter property="grid.column" targetname="contentpanel" value="0"/>
                                <setter property="width" targetname="columndefinition0" value="*"/>
                                <setter property="width" targetname="columndefinition1" value="auto"/>
                                <setter property="height" targetname="rowdefinition0" value="*"/>
                                <setter property="height" targetname="rowdefinition1" value="0"/>
                            </trigger>
                            <trigger property="isenabled" value="false">
                                <setter property="textelement.foreground" targetname="templateroot" value="{dynamicresource {x:static systemcolors.graytextbrushkey}}"/>
                            </trigger>
                        </controltemplate.triggers>
                    </controltemplate>
                </setter.value>
            </setter>
        </style>

即使这样设置了,tabcontrol的标题还是很丑,这个时候就需要通过设置tabitem来更改标题样式了。

tabitem样式如下:

<style x:key="tabitemstyle" targettype="{x:type tabitem}">
            <setter property="foreground" value="white"/>
            <setter property="background" value="transparent"/>
            <setter property="borderbrush" value="#ffacacac"/>
            <setter property="margin" value="0"/>
            <setter property="horizontalcontentalignment" value="stretch"/>
            <setter property="verticalcontentalignment" value="stretch"/>
            <setter property="template">
                <setter.value>
                    <controltemplate targettype="{x:type tabitem}">
                        <grid x:name="templateroot"  snapstodevicepixels="true" background="transparent">
                            <textblock x:name="txt" visibility="visible" verticalalignment="center" horizontalalignment="center" text="{templatebinding header}" tooltip="{templatebinding header}" foreground="{templatebinding foreground}" texttrimming="characterellipsis" />
                        </grid>
                        <controltemplate.triggers>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding ismouseover, relativesource={relativesource self}}" value="true"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>
                               
                                <setter property="foreground" targetname="txt" value="#fffea1"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="left"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="bottom"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="right"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>

                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isselected, relativesource={relativesource self}}" value="true"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>
                                <setter property="panel.zindex" value="1"/>
                                <setter property="foreground" targetname="txt" value="#fffea1"/>
                            </multidatatrigger>
                        </controltemplate.triggers>
                    </controltemplate>
                </setter.value>
            </setter>
        </style>

至此,样式已经设置完毕,引用示例:

   <grid background="#858586">
                <tabcontrol style="{staticresource tabcontrolstyle}" width="300" height="200" background="transparent" borderbrush="transparent" borderthickness="0">
                    <tabitem style="{staticresource tabitemstyle}" cursor="hand" header="音乐电台" height="38" >
                        <grid background="#33ffffff">
                            <textblock text="音乐电台" verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </tabitem>
                    <tabitem style="{staticresource tabitemstyle}" cursor="hand" header="mv电台" height="38" >
                        <grid background="#33ffffff">
                            <textblock text="mv电台" verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </tabitem>
                </tabcontrol>
            </grid>

效果如下:

三、实现tabcontrol标题居中显示(不平均分布)

同理需要更改tabcontrol的样式和tabitem的样式。需要把使用tabpanel作为标题的容器,设置horizontalalignment为center;

tabcontrol的样式如下:

 <style x:key="tabcontrolwithunderlinestyle" targettype="{x:type tabcontrol}">
        <setter property="padding" value="2"/>
        <setter property="horizontalcontentalignment" value="center"/>
        <setter property="verticalcontentalignment" value="center"/>
        <setter property="background" value="white"/>
        <setter property="borderbrush" value="#ffacacac"/>
        <setter property="borderthickness" value="1"/>
        <setter property="foreground" value="{dynamicresource {x:static systemcolors.controltextbrushkey}}"/>
        <setter property="template">
            <setter.value>
                <controltemplate targettype="{x:type tabcontrol}">
                    <grid x:name="templateroot" cliptobounds="true" snapstodevicepixels="true" keyboardnavigation.tabnavigation="local">
                        <grid.columndefinitions>
                            <columndefinition x:name="columndefinition0"/>
                            <columndefinition x:name="columndefinition1" width="0"/>
                        </grid.columndefinitions>
                        <grid.rowdefinitions>
                            <rowdefinition x:name="rowdefinition0" height="auto"/>
                            <rowdefinition x:name="rowdefinition1" height="*"/>
                        </grid.rowdefinitions>
                        <tabpanel x:name="headerpanel" horizontalalignment="center" background="transparent" grid.column="0" isitemshost="true" margin="0" grid.row="0" keyboardnavigation.tabindex="1" panel.zindex="1"/>
                        <line x1="0" x2="{binding actualwidth, relativesource={relativesource self}}" stroke="gray" strokethickness="0.1" verticalalignment="bottom" margin="0 0 0 1" snapstodevicepixels="true"/>
                        <border x:name="contentpanel" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}" grid.column="0" keyboardnavigation.directionalnavigation="contained" grid.row="1" keyboardnavigation.tabindex="2" keyboardnavigation.tabnavigation="local">
                            <contentpresenter x:name="part_selectedcontenthost" contenttemplate="{templatebinding selectedcontenttemplate}" content="{templatebinding selectedcontent}" contentstringformat="{templatebinding selectedcontentstringformat}" contentsource="selectedcontent" margin="0" snapstodevicepixels="{templatebinding snapstodevicepixels}"/>
                        </border>
                    </grid>
                    <controltemplate.triggers>
                        <trigger property="tabstripplacement" value="bottom">
                            <setter property="grid.row" targetname="headerpanel" value="1"/>
                            <setter property="grid.row" targetname="contentpanel" value="0"/>
                            <setter property="height" targetname="rowdefinition0" value="*"/>
                            <setter property="height" targetname="rowdefinition1" value="auto"/>
                        </trigger>
                        <trigger property="tabstripplacement" value="left">
                            <setter property="grid.row" targetname="headerpanel" value="0"/>
                            <setter property="grid.row" targetname="contentpanel" value="0"/>
                            <setter property="grid.column" targetname="headerpanel" value="0"/>
                            <setter property="grid.column" targetname="contentpanel" value="1"/>
                            <setter property="width" targetname="columndefinition0" value="auto"/>
                            <setter property="width" targetname="columndefinition1" value="*"/>
                            <setter property="height" targetname="rowdefinition0" value="*"/>
                            <setter property="height" targetname="rowdefinition1" value="0"/>
                        </trigger>
                        <trigger property="tabstripplacement" value="right">
                            <setter property="grid.row" targetname="headerpanel" value="0"/>
                            <setter property="grid.row" targetname="contentpanel" value="0"/>
                            <setter property="grid.column" targetname="headerpanel" value="1"/>
                            <setter property="grid.column" targetname="contentpanel" value="0"/>
                            <setter property="width" targetname="columndefinition0" value="*"/>
                            <setter property="width" targetname="columndefinition1" value="auto"/>
                            <setter property="height" targetname="rowdefinition0" value="*"/>
                            <setter property="height" targetname="rowdefinition1" value="0"/>
                        </trigger>
                        <trigger property="isenabled" value="false">
                            <setter property="textelement.foreground" targetname="templateroot" value="{dynamicresource {x:static systemcolors.graytextbrushkey}}"/>
                        </trigger>
                    </controltemplate.triggers>
                </controltemplate>
            </setter.value>
        </setter>
    </style>

tabitem样式如下:

 <style x:key="tabitemexwithunderlinestyle"  targettype="{x:type tabitem}">
            <setter property="foreground" value="white"/>
            <setter property="background" value="transparent"/>
            <setter property="borderbrush" value="#ffacacac"/>
            <setter property="margin" value="0"/>
            <setter property="horizontalcontentalignment" value="stretch"/>
            <setter property="verticalcontentalignment" value="stretch"/>
            <setter property="template">
                <setter.value>
                    <controltemplate targettype="{x:type tabitem}">
                        <grid x:name="templateroot"  snapstodevicepixels="true" background="transparent">
                            <border x:name="_underline" borderbrush="#37aefe" borderthickness="0" margin="{templatebinding margin}"/>
                            <grid>
                                <textblock x:name="txt" visibility="visible" verticalalignment="center" horizontalalignment="center" text="{templatebinding header}" tooltip="{templatebinding header}" foreground="{templatebinding foreground}" texttrimming="characterellipsis" />
                            </grid>
                        </grid>
                        <controltemplate.triggers>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding ismouseover, relativesource={relativesource self}}" value="true"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>

                                <setter property="foreground" targetname="txt" value="#37aefe"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="left"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="bottom"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="right"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isenabled, relativesource={relativesource self}}" value="false"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>
                                <setter property="opacity" targetname="templateroot" value="0.56"/>
                            </multidatatrigger>
                         
                            <multidatatrigger>
                                <multidatatrigger.conditions>
                                    <condition binding="{binding isselected, relativesource={relativesource self}}" value="true"/>
                                    <condition binding="{binding tabstripplacement, relativesource={relativesource findancestor, ancestorlevel=1, ancestortype={x:type tabcontrol}}}" value="top"/>
                                </multidatatrigger.conditions>
                                <setter property="panel.zindex" value="1"/>
                                <setter property="foreground" targetname="txt" value="#37aefe"/>
                                <setter property="borderthickness" targetname="_underline" value="0 0 0 2"/>
                            </multidatatrigger>
                        </controltemplate.triggers>
                    </controltemplate>
                </setter.value>
            </setter>
        </style>

引用示例:

<grid background="#858586">
                <tabcontrol style="{staticresource tabcontrolwithunderlinestyle}" foreground="black" width="300" height="200" background="transparent" borderbrush="transparent" borderthickness="0">
                    <tabitem style="{staticresource tabitemexwithunderlinestyle}" cursor="hand" header="音乐电台" height="38" width="70" margin="5 0">
                        <grid background="#33ffffff">
                            <textblock text="音乐电台" verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </tabitem>
                    <tabitem style="{staticresource tabitemexwithunderlinestyle}" cursor="hand" header="mv电台" height="38" width="70" margin="5 0">
                        <grid background="#33ffffff">
                            <textblock text="mv电台" verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </tabitem>
                </tabcontrol>
            </grid>

效果如下:

四、带关闭按钮的tabcontrol

 带关闭按钮的tabcontrol其实就是就是扩展tabitem,需要新建wpf自定义控件,命名为tabitemclose吧;

c#代码如下:

 public class tabitemclose : tabitem
    {
        static tabitemclose()
        {
            defaultstylekeyproperty.overridemetadata(typeof(tabitemclose), new frameworkpropertymetadata(typeof(tabitemclose)));
        }

        private static void onpropertychanged(dependencyobject d, dependencypropertychangedeventargs e)
        {
            d.setvalue(e.property, e.newvalue);
        }

        /// <summary>
        /// 是否可以关闭
        /// </summary>
        public bool iscanclose
        {
            get { return (bool)getvalue(iscancloseproperty); }
            set { setvalue(iscancloseproperty, value); }
        }

        public static readonly dependencyproperty iscancloseproperty =
            dependencyproperty.register("iscanclose", typeof(bool), typeof(tabitemclose), new propertymetadata(true, onpropertychanged));

        /// <summary>
        /// 关闭的图标
        /// </summary>
        public imagesource closeicon
        {
            get { return (imagesource)getvalue(closeiconproperty); }
            set { setvalue(closeiconproperty, value); }
        }

        public static readonly dependencyproperty closeiconproperty =
            dependencyproperty.register("closeicon", typeof(imagesource), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));



        /// <summary>
        /// 正常背景色
        /// </summary>
        public solidcolorbrush normalbackground
        {
            get { return (solidcolorbrush)getvalue(normalbackgroundproperty); }
            set { setvalue(normalbackgroundproperty, value); }
        }

        public static readonly dependencyproperty normalbackgroundproperty =
            dependencyproperty.register("normalbackground", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));

        /// <summary>
        /// 悬浮背景色
        /// </summary>
        public solidcolorbrush overbackgound
        {
            get { return (solidcolorbrush)getvalue(overbackgoundproperty); }
            set { setvalue(overbackgoundproperty, value); }
        }

        public static readonly dependencyproperty overbackgoundproperty =
            dependencyproperty.register("overbackgound", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));


        /// <summary>
        /// 选中背景色
        /// </summary>
        public solidcolorbrush selectedbackgound
        {
            get { return (solidcolorbrush)getvalue(selectedbackgoundproperty); }
            set { setvalue(selectedbackgoundproperty, value); }
        }

        public static readonly dependencyproperty selectedbackgoundproperty =
            dependencyproperty.register("selectedbackgound", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));


        /// <summary>
        /// 默认前景色
        /// </summary>
        public solidcolorbrush normalforeground
        {
            get { return (solidcolorbrush)getvalue(normalforegroundproperty); }
            set { setvalue(normalforegroundproperty, value); }
        }

        public static readonly dependencyproperty normalforegroundproperty =
            dependencyproperty.register("normalforeground", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));

        /// <summary>
        /// 悬浮前景色
        /// </summary>
        public solidcolorbrush overforeground
        {
            get { return (solidcolorbrush)getvalue(overforegroundproperty); }
            set { setvalue(overforegroundproperty, value); }
        }

        public static readonly dependencyproperty overforegroundproperty =
            dependencyproperty.register("overforeground", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));

        /// <summary>
        /// 选中前景色
        /// </summary>
        public solidcolorbrush selectedforeground
        {
            get { return (solidcolorbrush)getvalue(selectedforegroundproperty); }
            set { setvalue(selectedforegroundproperty, value); }
        }

        public static readonly dependencyproperty selectedforegroundproperty =
            dependencyproperty.register("selectedforeground", typeof(solidcolorbrush), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));


        /// <summary>
        /// 控件圆角
        /// </summary>
        public cornerradius cornerradius
        {
            get { return (cornerradius)getvalue(cornerradiusproperty); }
            set { setvalue(cornerradiusproperty, value); }
        }

        public static readonly dependencyproperty cornerradiusproperty =
            dependencyproperty.register("cornerradius", typeof(cornerradius), typeof(tabitemclose), new propertymetadata(new cornerradius(0), onpropertychanged));


        /// <summary>
        /// 前置logo
        /// </summary>
        public imagesource logoicon
        {
            get { return (imagesource)getvalue(logoiconproperty); }
            set { setvalue(logoiconproperty, value); }
        }

        public static readonly dependencyproperty logoiconproperty =
            dependencyproperty.register("logoicon", typeof(imagesource), typeof(tabitemclose), new propertymetadata(null, onpropertychanged));



        /// <summary>
        /// 前置logo宽度
        /// </summary>
        public double logoiconwidth
        {
            get { return (double)getvalue(logoiconwidthproperty); }
            set { setvalue(logoiconwidthproperty, value); }
        }

        public static readonly dependencyproperty logoiconwidthproperty =
            dependencyproperty.register("logoiconwidth", typeof(double), typeof(tabitemclose), new propertymetadata(double.parse("0"), onpropertychanged));


        /// <summary>
        /// 前置logo高度
        /// </summary>
        public double logoiconheigth
        {
            get { return (double)getvalue(logoiconheigthproperty); }
            set { setvalue(logoiconheigthproperty, value); }
        }

        public static readonly dependencyproperty logoiconheigthproperty =
            dependencyproperty.register("logoiconheigth", typeof(double), typeof(tabitemclose), new propertymetadata(double.parse("0"), onpropertychanged));


        /// <summary>
        /// logopadding
        /// </summary>
        public thickness logopadding
        {
            get { return (thickness)getvalue(logopaddingproperty); }
            set { setvalue(logopaddingproperty, value); }
        }

        public static readonly dependencyproperty logopaddingproperty =
            dependencyproperty.register("logopadding", typeof(thickness), typeof(tabitemclose), new propertymetadata(new thickness(0), onpropertychanged));

        /// <summary>
        /// 关闭item事件
        /// </summary>
        public event routedeventhandler closeitem
        {
            add { addhandler(closeitemevent, value); }
            remove { removehandler(closeitemevent, value); }
        }
        public static readonly routedevent closeitemevent =
            eventmanager.registerroutedevent("closeitem", routingstrategy.bubble, typeof(routedeventhandler), typeof(tabitemclose));

        /// <summary>
        /// 关闭项的右键菜单
        /// </summary>
        public contextmenu itemcontextmenu { get; set; }

        border itemborder;

        public override void onapplytemplate()
        {
            base.onapplytemplate();
            itemborder = template.findname("_bordertop", this) as border;
            if (itemcontextmenu != null)
            {
                itemborder.contextmenu = itemcontextmenu;
            }
        }
    }

这里面我们添加了很多扩展功能,包括右键菜单,图标显示和控件圆角,以及各种背景色属性。

然后为tabitemclose设置样式

 <style targettype="{x:type local:tabitemclose}">
        <setter property="horizontalcontentalignment" value="stretch"/>
        <setter property="verticalcontentalignment" value="stretch"/>
        <setter property="foreground" value="#666666"/>
        <setter property="margin" value="0 0 0 0"/>
        <setter property="padding" value="0"/>
        <setter property="borderthickness" value="0"/>
        <setter property="closeicon" value="/images/close2.png"/>
        <setter property="normalbackground" value="white"/>
        <setter property="overbackgound" value="#33ca5100"/>
        <setter property="selectedbackgound" value="#ca5100"/>
        <setter property="normalforeground" value="#555558"/>
        <setter property="overforeground" value="white"/>
        <setter property="selectedforeground" value="white"/>
        <setter property="template">
            <setter.value>
                <controltemplate targettype="{x:type local:tabitemclose}">
                    <border x:name="_bordertop"   width="{templatebinding width}" maxwidth="{templatebinding maxwidth}" height="{templatebinding height}" cornerradius="{templatebinding cornerradius}"  background="{templatebinding normalbackground}" borderthickness="{templatebinding borderthickness}" borderbrush="{templatebinding borderbrush}" tooltip="{templatebinding header}"  >
                        <dockpanel>
                            <image x:name="_logo" dockpanel.dock="left" visibility="visible" margin="{templatebinding logopadding}" source="{templatebinding logoicon}" verticalalignment="center"  horizontalalignment="center" stretch="uniform" width="{templatebinding logoiconwidth}" height="{templatebinding logoiconheigth}" />
                            <grid name="_grid" snapstodevicepixels="true">
                                <grid.columndefinitions>
                                    <columndefinition width="*" />
                                    <columndefinition x:name="_col_close" width="20" />
                                </grid.columndefinitions>
                                <border grid.columnspan="2" background="white" opacity="0"/>
                                <textblock   x:name="_txt" verticalalignment="center" texttrimming="characterellipsis"  margin="3 0 3 0"   foreground="{templatebinding normalforeground}" textalignment="center" horizontalalignment="center"   text="{templatebinding header}"  />
                                <grid x:name="_gridclose" grid.column="1"  >
                                    <border x:name="_borderbg" background="black" opacity="0" />
                                    <local:buttonex x:name="part_close_tabitem" horizontalalignment="center"  verticalalignment="center" background="transparent" visibility="visible" icon="{templatebinding closeicon}"  buttontype="icon" />
                                </grid>
                            </grid>
                        </dockpanel>

                    </border>
                    <controltemplate.triggers>
                        <trigger property="logoicon" value="{x:null}">
                            <setter targetname="_logo" property="visibility" value="collapsed" />
                        </trigger>
                        <trigger property="iscanclose" value="false">
                            <setter targetname="_gridclose" property="visibility" value="collapsed"/>
                            <setter targetname="_col_close" property="width" value="0"/>
                        </trigger>
                        <trigger property="isselected" value="true">
                            <setter targetname="_bordertop" property="background" value="{binding selectedbackgound,relativesource={relativesource templatedparent}}" />
                            <setter targetname="_txt"  property="foreground" value="{binding selectedforeground,relativesource={relativesource templatedparent}}"/>
                        </trigger>
                        <multitrigger>
                            <multitrigger.conditions>
                                <condition property="ismouseover" value="true"/>
                                <condition property="isselected" value="false"/>
                            </multitrigger.conditions>
                            <setter targetname="_txt"  property="foreground" value="{binding overforeground,relativesource={relativesource templatedparent}}"/>
                            <setter targetname="_bordertop"  property="background" value="{binding overbackgound,relativesource={relativesource templatedparent}}"/>
                        </multitrigger>
                    </controltemplate.triggers>
                </controltemplate>
            </setter.value>
        </setter>
    </style>

这里面使用了一个close的图标

tabcontrol的图标可设置可不设置,看自己需要。

这里面还用到了前面讲的控件buttonex,定义方法我就不重复赘述了。大家可以通过这个链接跳转查看:http://www.cnblogs.com/xiaomingg/p/8699125.html。buttonex.cs里面还要添加几个方法用来支持关闭tabitem:

 protected override void onclick()
        {
            base.onclick();

            if (!string.isnullorempty(name) && name == "part_close_tabitem")
            {
                tabitemclose itemclose = findvisualparent<tabitemclose>(this);
                (itemclose.parent as tabcontrol).items.remove(itemclose);
                routedeventargs args = new routedeventargs(tabitemclose.closeitemevent, itemclose);
                itemclose.raiseevent(args);
            }

        }

        public static t findvisualparent<t>(dependencyobject obj) where t : class
        {
            while (obj != null)
            {
                if (obj is t)
                    return obj as t;

                obj = visualtreehelper.getparent(obj);
            }

            return null;
        }

引用示例:

 <grid background="#858586">
                <tabcontrol  foreground="black" width="300" height="200" background="transparent" borderbrush="transparent" borderthickness="0">
                    <local:tabitemclose  cursor="hand" header="音乐电台" height="20"  width="100">
                        <grid background="#aaffffff">
                            <textblock text="音乐电台"  verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </local:tabitemclose>
                    <local:tabitemclose  cursor="hand" header="mv电台" height="20" width="100">
                        <grid background="#aaffffff">
                            <textblock text="mv电台" verticalalignment="center" horizontalalignment="center"/>
                        </grid>
                    </local:tabitemclose>
                </tabcontrol>
            </grid>

效果如下:

所有代码已经上传到github:https://github.com/cmfgit/wpfdemo.git

 

《WPF 自定义TabControl控件样式.doc》

下载本文的Word格式文档,以方便收藏与打印。