I ran into this problem and found a workaround that lets you design custom controls using Xaml. Its still has a bit of a hack, but one that solved all of my problems without any obvious compromises.
Basically, you do everything the way you normally would with the xaml, but you also include some of the header declarations on the control template itself and Base64 encode that template to be loaded in the code constructor. Not shown in this Xaml excerpt, but the namespace my full Xaml used is actually targeting a XamlTemplates instead of the Controls namespace. This was on purpose because the "Release" build moves that developmental Debug reference out of the way from my production controls namespace. More on that below.
<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>
I'll point out that the above XAML didn't name the control it derived from and everything within the template used relative lookups to bind its properties; even the custom ones.
On the C# side, I used the Base64 encoded version of the control template from my Xaml and directives to shuffle around the development/release versions of the controls. The theory being that my controls in the development space wouldn't run into the problem this topic is about, but would give me a way to test/develop them. The release DLL versions seem to be working really well and the controls built do have great design time support just like they did on the Debug/Development side.
#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());
}
}
The critical thing to remember before creating a "release" control DLL to be used in your primary application is to update your base64 encoded string with your latest and greatest version of its control template. This is because the Release build is completely detached from the original Xaml and entirely dependent on the encoded one.
The above control and others like it can be found on GitHub. Which is a library I am making intended to "unlock" many of the things I want to style that standard controls don't expose. That and adding some features that don't exist. For example, the above TabControl has an additional content property for utilizing the "unused" area of the tab headers.
Important Notes:
- Basic styling gets lost using this method, but you get it all back if your styles for the Custom Control uses the
BasedOn="{StaticResource {x:Type TabControl}}"
mechanism.
- I need to find time to research if this will cause any noteworthy memory leaks and whether I can do anything to combat them, if anyone has any thoughts on this let me know in the comments.