как растянуть заголовки элемента вкладки wpf до ширины родительского элемента управления

<p>есть ли способ в xaml, чтобы заголовки элементов вкладок растягивались по ширине элемента управления вкладками?</p><p>например, у меня есть три вкладки: красная, синяя и зеленая. если у меня есть элемент управления вкладкой с его шириной, установленной на auto, заголовки вкладок будут заполнять только часть пространства над содержимым вкладок, но я хочу, чтобы они заполняли все пространство. для моего примера с тремя вкладками красный цвет должен занимать первую треть элемента управления, синий должен занимать центральную треть, а зеленый - последнюю треть.</p><p>у меня есть идея, как сделать это в коде, над которым я сейчас работаю, но я заинтересован в том, чтобы сделать это как можно проще.</p>

<p>я взял пример джордана и внес в него некоторые изменения. эта версия должна работать для любого количества вкладок:</p><pre><code>namespace wpfapplication1.converters { public class tabsizeconverter : imultivalueconverter { public object convert(object[] values, type targettype, object parameter, system.globalization.cultureinfo culture) { tabcontrol tabcontrol = values[0] as tabcontrol; double width = tabcontrol.actualwidth / tabcontrol.items.count; //subtract 1, otherwise we could overflow to two rows. return (width <= 1) ? 0 : (width - 1); } public object[] convertback(object value, type[] targettypes, object parameter, system.globalization.cultureinfo culture) { throw new notsupportedexception(); } } } </code></pre><p>то же пространство имен в xaml:</p><pre><code>xmlns:local="clr-namespace:wpfapplication1.converters" </code></pre><p>и это заставит все вкладки использовать его:</p><pre><code><window.resources> <local:tabsizeconverter x:key="tabsizeconverter" /> <style targettype="{x:type tabitem}"> <setter property="width"> <setter.value> <multibinding converter="{staticresource tabsizeconverter}"> <binding relativesource="{relativesource mode=findancestor, ancestortype={x:type tabcontrol}}" /> <binding relativesource="{relativesource mode=findancestor, ancestortype={x:type tabcontrol}}" path="actualwidth" /> </multibinding> </setter.value> </setter> </style> </window.resources> </code></pre>

<p>кажется, что все идут по пути конвертации, но на самом деле это так же просто, как использовать <code>uniformgrid</code> с <code>rows</code>, установленным в 1 в шаблоне <code>tabcontrol</code>, вместо <code>tabpanel</code>. конечно, вам придется повторно шаблонировать его, но это не так уж плохо.</p>

<p>я смог сделать это с помощью конвертера следующим образом:</p><pre><code>namespace wpfapplication1.converters { public class sizeconverter : ivalueconverter { #region ivalueconverter members public object convert(object value, type targettype, object parameter, system.globalization.cultureinfo culture) { double width = double.parse(value.tostring()); //subtract 1, otherwise we could overflow to two rows. return .25 * width - 1; } public object convertback(object value, type targettype, object parameter, system.globalization.cultureinfo culture) { throw new notsupportedexception(); } #endregion } } </code></pre><p>затем добавьте пространство имен в мой xaml:</p><pre><code>xmlns:local="clr-namespace:wpfapplication1.converters" </code></pre><p>затем, заставляя все tabitems использовать конвертер:</p><pre><code><window.resources> <local:sizeconverter x:key="sizeconverter" /> <style targettype="{x:type tabitem}"> <setter property="width" value="{binding elementname=x\_grid, path=actualwidth, converter={staticresource sizeconverter}}" /> </style> </window.resources> </code></pre><p>x\_grid - это x: имя родительского элемента, для которого вкладки должны быть 1/4, если это имеет смысл.</p>

<p>это возможно, привязав ширину к actualwidth родительского элемента управления вкладками, как показано ниже.</p><p>я обернул его в стиле, чтобы применить ко всем страницам вкладок. </p><pre><code><grid> <grid.resources> <style targettype="tabitem"> <setter property="width" value="{binding path=actualwidth, relativesource={relativesource mode=findancestor, ancestortype={x:type tabcontrol}}}"/> </style> </grid.resources> <tabcontrol> <tabitem header="page3"/> <tabitem header="page2"/> <tabitem header="page3"/> </tabcontrol> </grid> </code></pre>

<p>я парень в старом стиле. и предпочитают этот вид функциональности для инкапсуляции в код самого элемента управления. мой производный элемент управления выглядит следующим образом:</p><pre><code> public class customtabcontrol :tabcontrol { protected override void onrendersizechanged(system.windows.sizechangedinfo sizeinfo) { foreach (tabitem item in this.items) { double neww = (this.actualwidth / items.count) - 1; if (neww < 0) neww = 0; item.width = neww; } } } </code></pre><p>и мой xaml выглядит</p><pre><code></infrastructure:customtabcontrol> <tabitem /> <tabitem /> </infrustracture:customcontrol> </code></pre><p>может кто-нибудь объяснить, почему все предпочитают управление стилем, а не производное.</p>

<p>я последовал предложению чарли и пошел по шаблону. вот простая реализация <code>tabcontrol</code>, которая делит доступное пространство поровну между своими <code>tabitem</code>s, используя <code>uniformgrid</code>:</p><p><strong> xaml управления </strong></p><pre><code><tabcontrol x:class="yournamespace.views.bigtabstabcontrol" 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:yournamespace.views" mc:ignorable="d" d:designheight="300" d:designwidth="300" padding="2" horizontalcontentalignment="center" verticalcontentalignment="center" borderthickness="1" foreground="{dynamicresource {x:static systemcolors.controltextbrushkey}}"> <tabcontrol.resources> <solidcolorbrush x:key="tabitem.selected.background" color="#ffffff"/> <solidcolorbrush x:key="tabitem.selected.border" color="#acacac"/> </tabcontrol.resources> <tabcontrol.style> <style targettype="{x:type tabcontrol}"> <setter property="background" value="{staticresource tabitem.selected.background}"/> <setter property="borderbrush" value="{staticresource tabitem.selected.border}"/> </style> </tabcontrol.style> <tabcontrol.template> <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" background="transparent" grid.column="0" isitemshost="true" grid.row="0" keyboardnavigation.tabindex="1" panel.zindex="1" /> <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" contentsource="selectedcontent" margin="{templatebinding padding}" 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"/> <setter property="margin" targetname="headerpanel" value="2,0,2,2"/> </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"/> <setter property="margin" targetname="headerpanel" value="2,2,0,2"/> </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"/> <setter property="margin" targetname="headerpanel" value="0,2,2,2"/> </trigger> <trigger property="isenabled" value="false"> <setter property="textelement.foreground" targetname="templateroot" value="{dynamicresource {x:static systemcolors.graytextbrushkey}}"/> </trigger> </controltemplate.triggers> </controltemplate> </tabcontrol.template> </tabcontrol> </code></pre><p><strong> код контроля позади </strong></p><pre><code>using system.windows.controls; using system.windows.controls.primitives; namespace yournamespace.views { /// <summary> /// a tabcontrol with large tabs. /// </summary> public partial class bigtabstabcontrol : tabcontrol { public bigtabstabcontrol() { initializecomponent(); } public override void onapplytemplate() { base.onapplytemplate(); if (this.template != null) { uniformgrid x = this.template.findname("headerpanel", this) as uniformgrid; if (x != null) x.columns = this.items.count; } } } } </code></pre><p>вот и все. теперь вы можете добавить <code>tabitem</code>s к этому элементу управления, и они автоматически настроят свою ширину. нет необходимости указывать <code>grid.column</code> для этих <code>tabitem</code>s, они прекрасно работают без него даже во время разработки.</p>

<p>вот безболезненное решение, которое использует только шаблоны:</p><pre><code><window x:class="window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:effectlibrary="clr-namespace:effectlibrary;assembly=effectlibrary" mc:ignorable="d" title="window1" height="300" width="300"> <tabcontrol style="{dynamicresource tabcontrolstyle}" itemcontainerstyle="{dynamicresource tabitemstyle}" borderbrush="{dynamicresource pallete.primary}" foreground="{dynamicresource pallete.primary}" background="transparent" margin="0" d:layoutoverrides="height"> <tabcontrol.resources> <style x:key="tabcontrolstyle" targettype="{x:type tabcontrol}"> <setter property="padding" value="0"/> <setter property="horizontalcontentalignment" value="center"/> <setter property="verticalcontentalignment" value="center"/> <setter property="background" value="transparent"/> <setter property="borderbrush" value="#093a5f"/> <setter property="borderthickness" value="1"/> <setter property="foreground" value="#001423"/> <setter property="template"> <setter.value> <controltemplate targettype="{x:type tabcontrol}"> <border x:name="bg" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}"> <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" isitemshost="true" margin="0"> <uniformgrid.style> <style targettype="{x:type uniformgrid}"> <setter property="rows" value="1"/> <style.triggers> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource templatedparent}}" value="right"> <setter property="columns" value="1"/> <setter property="rows" value="0"/> </datatrigger> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource templatedparent}}" value="left"> <setter property="columns" value="1"/> <setter property="rows" value="0"/> </datatrigger> </style.triggers> </style> </uniformgrid.style> </uniformgrid> <border x:name="contentpanel" grid.column="0" keyboardnavigation.directionalnavigation="contained" grid.row="1" keyboardnavigation.tabindex="2" keyboardnavigation.tabnavigation="local" borderthickness="0,1,0,0" borderbrush="{templatebinding borderbrush}"> <contentpresenter x:name="part\_selectedcontenthost" contentsource="selectedcontent" margin="{templatebinding padding}" snapstodevicepixels="{templatebinding snapstodevicepixels}"/> </border> </grid> </border> <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="effect" targetname="templateroot"> <setter.value> <effectlibrary:desaturateeffect desaturationfactor=".25"/> </setter.value> </setter> </trigger> </controltemplate.triggers> </controltemplate> </setter.value> </setter> </style> <style x:key="tabitemstyle" targettype="{x:type tabitem}"> <setter property="focusvisualstyle" value="{staticresource focusvisual}"/> <setter property="foreground" value="{binding foreground, relativesource={relativesource findancestor, ancestortype={x:type tabcontrol}}}"/> <setter property="background" value="{binding background, relativesource={relativesource findancestor, ancestortype={x:type tabcontrol}}}"/> <setter property="borderbrush" value="{binding borderbrush, relativesource={relativesource findancestor, ancestortype={x:type tabcontrol}}}"/> <setter property="margin" value="0"/> <setter property="padding" value="0,5"/> <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="{templatebinding background}"> <border x:name="mainborder" borderbrush="{templatebinding borderbrush}"> <border x:name="highlightborder"/> </border> <contentpresenter x:name="contentpresenter" contentsource="header" focusable="false" horizontalalignment="{binding horizontalcontentalignment, relativesource={relativesource ancestortype={x:type itemscontrol}}}" margin="{templatebinding padding}" recognizesaccesskey="true" snapstodevicepixels="{templatebinding snapstodevicepixels}" verticalalignment="{binding verticalcontentalignment, relativesource={relativesource ancestortype={x:type itemscontrol}}}"/> </grid> <controltemplate.triggers> <trigger property="isenabled" value="false"> <setter property="effect" targetname="templateroot"> <setter.value> <effectlibrary:desaturateeffect desaturationfactor=".25"/> </setter.value> </setter> </trigger> <datatrigger binding="{binding isselected, relativesource={relativesource self}}" value="true"> <setter targetname="highlightborder" property="background" value="#0b79ce"/> </datatrigger> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource ancestortype={x:type tabcontrol}}}" value="top"> <setter targetname="mainborder" property="borderthickness" value="0,0,1,0"/> <setter targetname="highlightborder" property="height" value="2"/> <setter targetname="highlightborder" property="verticalalignment" value="bottom"/> </datatrigger> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource ancestortype={x:type tabcontrol}}}" value="bottom"> <setter targetname="mainborder" property="borderthickness" value="0,0,1,0"/> <setter targetname="highlightborder" property="height" value="2"/> <setter targetname="highlightborder" property="verticalalignment" value="top"/> </datatrigger> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource ancestortype={x:type tabcontrol}}}" value="left"> <setter targetname="mainborder" property="borderthickness" value="0,0,0,1"/> <setter targetname="highlightborder" property="width" value="2"/> <setter targetname="highlightborder" property="horizontalalignment" value="right"/> </datatrigger> <datatrigger binding="{binding tabstripplacement, relativesource={relativesource ancestortype={x:type tabcontrol}}}" value="right"> <setter targetname="mainborder" property="borderthickness" value="0,0,0,1"/> <setter targetname="highlightborder" property="width" value="2"/> <setter targetname="highlightborder" property="horizontalalignment" value="left"/> </datatrigger> </controltemplate.triggers> </controltemplate> </setter.value> </setter> </style> </tabcontrol.resources> <tabitem header="years"> <listbox background="{dynamicresource pallete.primary.brightest}" foreground="{dynamicresource pallete.primary}"> <textblock text="2015"/> <textblock text="2016"/> <textblock text="2017"/> </listbox> </tabitem> <tabitem header="tables"> <listbox background="{dynamicresource pallete.primary.brightest}" foreground="{dynamicresource pallete.primary}"> <textblock text="table1..."/> <textblock text="table2..."/> <textblock text="table3..."/> </listbox> </tabitem> </tabcontrol> </window> </code></pre><p>надеюсь, я включил все цвета, и это будет работать для вас. ааа ... щелк! мой эффект десатурации! <a href="https://github.com/zorg-alex/zwpf-startup-project">мой стартовый проект wpf</a> вы можете получить этот эффект оттуда, если хотите (эффект проще отобразить в триггере, чем перекрасить все то же самое с бликами) . да, это много кода , но я просто изменил itemscontainer, чтобы он выглядел лучше, и заменил стандартный элемент управления header на uniformgrid и установил rows или columns в 1 в зависимости от tabstripplacement. теперь я могу свернуть этот код или спрятать его где-нибудь. :)</p>

<p>я решил эту проблему, создав специальный конвертер:</p><pre><code> public class tabitemwidthadjustmentconverter : ivalueconverter { public object convert(object value, type targettype, object parameter, system.globalization.cultureinfo culture) { double ltabcontrolwidth = value is double ? (double)value : 50; // 50 just to see something, in case of error int32 ltabscount = (parameter != null && parameter is string) ? int32.parse((string)parameter) : 1; return ltabcontrolwidth / ltabscount; } public object convertback(object value, type targettype, object parameter, system.globalization.cultureinfo culture) { throw new notimplementedexception(); } } </code></pre><p>и я вычисляю значение одного элемента вкладки в элементе tag элемента tabcontrol, чтобы избежать его расчета для каждой вкладки в отдельности. вот пример кода (обратите внимание, что в моем случае мне нужен горизонтальный scrollviewer, потому что у меня есть несколько элементов вкладок, и с минимальной шириной):</p><pre><code><tabcontrol name="tabcontrol" verticalalignment="stretch" selectionchanged="tabcontrol\_selectionchanged" tag="{binding relativesource={relativesource self}, path=actualwidth, converter={staticresource tabitemwidthadjustmentconverter}, converterparameter=15}"><!-- here 15 because i have 15 tabs --> <tabcontrol.template> <controltemplate targettype="tabcontrol"> <stackpanel> <scrollviewer horizontalscrollbarvisibility="auto" verticalscrollbarvisibility="disabled"> <tabpanel x:name="headerpanel" panel.zindex="1" keyboardnavigation.tabindex="1" isitemshost="true"/> </scrollviewer> <contentpresenter x:name="part\_selectedcontenthost" snapstodevicepixels="{templatebinding snapstodevicepixels}" margin="{templatebinding padding}" contentsource="selectedcontent"/> </stackpanel> </controltemplate> </tabcontrol.template> <tabitem header="tab1" minwidth="115" verticalalignment="stretch" width="{binding elementname=tabcontrol, path=tag}"> <contentcontrol contenttemplate="{staticresource my\_templatetab1}"> <contentpresenter /> </contentcontrol> </tabitem> <tabitem header="tab2" minwidth="115" height="50" width="{binding elementname=tabcontrol, path=tag}"> <contentcontrol contenttemplate="{staticresource my\_templatetab2}"> <contentpresenter /> </contentcontrol> </tabitem> <!-- here another 13 tabs which i skipped --> </tabcontrol> </code></pre><p>я могу сказать, что это работает как шарм в моем случае:) надеюсь, кто-то найдет это полезным!</p><p>постскриптум мне не нужно/не нужно никакого стиля в моем случае.</p>

<p>я использую следующее решение: в главном окне я использую событие измененного размера окна и событие инициализации tabcontrol, чтобы установить ширину каждой вкладки. число «5» соответствует моему количеству вкладок. </p><pre><code> private void tabchanger\_initialized(object sender, eventargs e) { foreach (tabitem item in tabchanger.items) { double neww = (tabchanger.actualwidth / 5) - 1; if (neww < 0) neww = 0; item.width = neww; } } private void window\_sizechanged(object sender, sizechangedeventargs e) { foreach (tabitem item in tabchanger.items) { double neww = (tabchanger.actualwidth / 5) - 1; if (neww < 0) neww = 0; item.width = neww; } } </code></pre>

<p>я не знаю, будет ли это работать для вкладок, но всякий раз, когда мне нужно было растянуть что-либо, чтобы заполнить контейнер, я использовал <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox.aspx">viewbox</a> . это то, что вы ищете?</p>

<p>в дополнение к общепринятому решению райана версау, которое дает одинаковую ширину заголовка tabitem, я нашел следующий способ сделать его зависимым от длины каждого заголовка.</p><p>сначала мы получаем строку каждого заголовка tabitem, добавляя эту строку в мультисвязь xaml. таким образом, это становится:</p><pre><code><multibinding converter="{staticresource tabsizeconverter}"> <binding relativesource="{relativesource mode=findancestor, ancestortype={x:type tabcontrol}}" /> <binding relativesource="{relativesource mode=findancestor, ancestortype={x:type tabcontrol}}" path="actualwidth" /> <binding path="header" relativesource="{relativesource self}"/> </multibinding> </code></pre><p>и еще немного кода в конвертере (values [] получает также заголовок tabitem):</p><pre><code>public object convert(object[] values, type targettype, object parameter, system.globalization.cultureinfo culture) { tabcontrol tabcontrol = values[0] as tabcontrol; string allheaders = ""; for (int i = 0; i < tabcontrol.items.count; i++) { int index = tabcontrol.items[i].tostring().indexof("header:") + "header:".length; string currentheader = tabcontrol.items[i].tostring().substring(index); currentheader = currentheader.substring(0, currentheader.length - " content:".length); allheaders += currentheader; } //normalize width according to header length double width = values[2].tostring().length * tabcontrol.actualwidth / allheaders.length; //subtract 1, otherwise we could overflow to two rows. var retval = (width <= 1) ? 0 : (width - 1); return retval; } </code></pre><p>я подозреваю, что может быть более эффективный способ получить строку allheaders всех заголовков, но она работает нормально, как есть ...</p>