我遇到了这个问题并找到了一种解决方法,可以让您使用 Xaml 设计自定义控件。它仍然有一些 hack,但它解决了我所有的问题,没有任何明显的妥协。
基本上,您可以按照通常使用 xaml 的方式执行所有操作,但您还可以在控件模板本身上包含一些标头声明,并对要在代码构造函数中加载的模板进行 Base64 编码。此 Xaml 摘录中未显示,但我使用的完整 Xaml 命名空间实际上是针对 XamlTemplates 而不是 Controls 命名空间。这是故意的,因为“发布”构建将开发调试引用从我的生产控制命名空间中移开。更多内容如下。
<ControlTemplate TargetType="{x:Type TabControl}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="templateRoot"
ClipToBounds="True"
SnapsToDevicePixels="True"
Background="Transparent"
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"
Panel.ZIndex="1"
Margin="{Binding MarginHeaderPanel, RelativeSource={RelativeSource AncestorType=TabControl}}"
Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
IsItemsHost="True"
KeyboardNavigation.TabIndex="2"/>
<Border x:Name="blankregion" Panel.ZIndex="1" Margin="0" Padding="0"
Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}">
<ContentPresenter x:Name="blankpresenter"
KeyboardNavigation.TabIndex="1"
Content="{Binding TabBlankSpaceContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
ContentSource="TabBlankSpaceContent"
SnapsToDevicePixels="True"/>
</Border>
<Grid x:Name="ContentPanel">
<Border
BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TabControl}}"
BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=TabControl}}"
Background="{Binding SelectedItem.Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabNavigation="Local"
CornerRadius="{Binding BorderRadius, RelativeSource={RelativeSource AncestorType=TabControl}}"
KeyboardNavigation.TabIndex="3">
<ContentControl x:Name="PART_SelectedContentHost"
ContentTemplate="{Binding SelectedContentTemplate, RelativeSource={RelativeSource AncestorType=TabControl}}"
Content="{Binding SelectedContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
ContentStringFormat="{Binding SelectedContentStringFormat, RelativeSource={RelativeSource AncestorType=TabControl}}"
Margin="{Binding Padding, RelativeSource={RelativeSource AncestorType=TabControl}}"
SnapsToDevicePixels="{Binding SnapsToDevicePixels, RelativeSource={RelativeSource AncestorType=TabControl}}"/>
</Border>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<!--Triggers were removed for clarity-->
</ControlTemplate.Triggers>
</ControlTemplate>
我要指出的是,上面的 XAML 没有命名它派生的控件,并且模板中的所有内容都使用相对查找来绑定其属性;甚至是定制的。
在 C# 方面,我使用 Xaml 中的控件模板的 Base64 编码版本和指令来调整控件的开发/发布版本。理论是我在开发空间中的控件不会遇到这个主题所涉及的问题,但会给我一种测试/开发它们的方法。发布的 DLL 版本似乎运行良好,并且所构建的控件确实具有很好的设计时支持,就像它们在调试/开发方面所做的那样。
#if DEBUG
namespace AgileBIM.Controls
{
public class AgileTabControl : AgileBIM.XamlTemplates.AgileTabControlDesigner { }
}
namespace AgileBIM.XamlTemplates
#else
namespace AgileBIM.Controls
#endif
{
#if DEBUG
public partial class AgileTabControlDesigner : TabControl
#else
public class AgileTabControl : TabControl
#endif
{
#if DEBUG
private static Type ThisControl = typeof(AgileTabControlDesigner);
#else
private static Type ThisControl = typeof(AgileTabControl);
private string Template64 = "Base64 encoded template removed for clarity"
#endif
#if DEBUG
public AgileTabControlDesigner() { InitializeComponent(); }
#else
public AgileTabControl()
{
string decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Template64));
System.IO.StringReader sr = new System.IO.StringReader(decoded);
System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr);
ControlTemplate ct = (ControlTemplate)System.Windows.Markup.XamlReader.Load(xr);
DefaultStyleKey = ThisControl;
Template = ct;
}
#endif
public Thickness MarginHeaderPanel
{
get { return (Thickness)GetValue(MarginHeaderPanelProperty); }
set { SetValue(MarginHeaderPanelProperty, value); }
}
public static readonly DependencyProperty MarginHeaderPanelProperty =
DependencyProperty.Register("MarginHeaderPanel", typeof(Thickness), ThisControl, new PropertyMetadata(new Thickness(0)));
public CornerRadius BorderRadius
{
get { return (CornerRadius)GetValue(BorderRadiusProperty); }
set { SetValue(BorderRadiusProperty, value); }
}
public static readonly DependencyProperty BorderRadiusProperty =
DependencyProperty.Register("BorderRadius", typeof(CornerRadius), ThisControl, new PropertyMetadata(new CornerRadius(0)));
public object TabBlankSpaceContent
{
get { return (object)GetValue(TabBlankSpaceContentProperty); }
set { SetValue(TabBlankSpaceContentProperty, value); }
}
public static readonly DependencyProperty TabBlankSpaceContentProperty =
DependencyProperty.Register("TabBlankSpaceContent", typeof(object), ThisControl, new PropertyMetadata());
}
}
在创建要在主应用程序中使用的“发布”控件 DLL 之前要记住的关键事项是使用最新和最好的控件模板版本更新您的 base64 编码字符串。这是因为 Release 版本与原始 Xaml 完全分离,完全依赖于编码版本。
上面的控件和其他类似的控件可以在GitHub上找到。这是我正在制作的一个库,旨在“解锁”许多我想要设置标准控件不公开的样式的东西。那并添加一些不存在的功能。例如,上面的 TabControl 有一个额外的 content 属性,用于利用选项卡标题的“未使用”区域。
重要提示:
- 使用此方法会丢失基本样式,但如果您的自定义控件样式使用
BasedOn="{StaticResource {x:Type TabControl}}" 机制,您可以恢复所有样式。
- 我需要花时间研究这是否会导致任何值得注意的内存泄漏,以及我是否可以采取任何措施来解决它们,如果有人对此有任何想法,请在 cmets 中告诉我。