TabControl 继承的 CustomControl 不显示任何内容

TabControl inherited CustomControl doesn't show anything

首先,我是一名非英语母语的业余爱好者,所以如果您能耐心等待我,我将不胜感激 ;)

在另一个 Whosebug 问题之后,我已经完成了一个 TabControl 扩展自身(就像一个扩展器)只是修改了原始 TabControl 的模板,所以我可以在选择任何选项卡时改变控件的高度。
效果很好,但逻辑需要一些 ViewModel 属性。现在我需要再次使用该控件,我认为更好的方法是制作一个 CustomControl。

我遇到的问题是,尽管我在 Generic.xaml 文件和 Build Action 属性 到 Page,修改了 AsemblyInfo.cs,并在静态构造函数中使用了 DefaultStyleKeyProperty.OverrideMetadata。以下所有其他问题建议。

我认为最好展示我现在的代码,然后解释我还尝试了什么,我不得不说当我注意到这个时我正在写逻辑,所以它不能正常工作现在。

Generic.xaml:

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PRUEBA_TABBEDEXPANDER"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"  
xmlns:ie="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
xmlns:Core="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions" 
xmlns:int="clr-namespace:System.Windows.Interactivity">

<SolidColorBrush x:Key="TabControlNormalBorderBrush" Color="#8C8E94"/>
<Style x:Key="TabItemFocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Rectangle Margin="3,3,3,1" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#F3F3F3" Offset="0"/>
    <GradientStop Color="#EBEBEB" Offset="0.5"/>
    <GradientStop Color="#DDDDDD" Offset="0.5"/>
    <GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="TabItemHotBackground" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#EAF6FD" Offset="0.15"/>
    <GradientStop Color="#D9F0FC" Offset=".5"/>
    <GradientStop Color="#BEE6FD" Offset=".5"/>
    <GradientStop Color="#A7D9F5" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="TabItemSelectedBackground" Color="#F9F9F9"/>
<SolidColorBrush x:Key="TabItemHotBorderBrush" Color="#3C7FB1"/>
<SolidColorBrush x:Key="TabItemDisabledBackground" Color="#F4F4F4"/>
<SolidColorBrush x:Key="TabItemDisabledBorderBrush" Color="#FFC9C7BA"/>
<Style x:Key="TabItemStyle1" TargetType="{x:Type TabItem}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="Padding" Value="6,1,6,1"/>
    <Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
    <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Grid SnapsToDevicePixels="true">
                    <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
                        <ToggleButton x:Name="Content" Content="{TemplateBinding Header}">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="Click">
                                    <ei:CallMethodAction MethodName="ToggleButton_Click"
                                                         TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </ToggleButton>
                        <!--<ContentPresenter x:Name="Content" ContentSource="Header" 
                            HorizontalAlignment="{Binding HorizontalContentAlignment, 
                            RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" 
                            RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                            VerticalAlignment="{Binding VerticalContentAlignment, 
                            RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>-->
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemHotBackground}"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="true">
                        <Setter Property="Panel.ZIndex" Value="1"/>
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemSelectedBackground}"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="false"/>
                            <Condition Property="IsMouseOver" Value="true"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource TabItemHotBorderBrush}"/>
                    </MultiTrigger>
                    <Trigger Property="TabStripPlacement" Value="Bottom">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,0,1,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Left">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,0,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Right">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="0,1,1,1"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Top"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-2,-1"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,0,0,1"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Bottom"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-1,-2,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,1,0,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Left"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-1,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,0,1,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Right"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-1,-2,-2,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="1,0,0,0"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemDisabledBackground}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource TabItemDisabledBorderBrush}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="TabbedExpanderTabControlStyle" TargetType="{x:Type TabControl}">
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="Padding" Value="4,4,4,4"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
    <Setter Property="Background" Value="#F9F9F9"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="ItemContainerStyle" Value="{StaticResource TabItemStyle1}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid 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" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" 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="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="TabbedExpanderStyle" TargetType="{x:Type local:TabbedExpander}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:TabbedExpander}">
                <Grid>
                    <TabControl Style="{StaticResource TabbedExpanderTabControlStyle}"></TabControl>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

TabbedExpander.cs:

public class TabbedExpander : TabControl, INotifyPropertyChanged
{
    static TabbedExpander()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(TabbedExpander), new FrameworkPropertyMetadata(typeof(TabbedExpander)));
    }

    #region dependency properties


    public ObservableCollection<object> ItemsS
    {
        get { return (ObservableCollection<object>)GetValue(ItemsSProperty); }
        set { SetValue(ItemsSProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemsS.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemsSProperty =
        DependencyProperty.Register("ItemsS", typeof(ObservableCollection<object>), typeof(TabbedExpander), 
            new PropertyMetadata(new ObservableCollection<object>(), OnItemsSPropertyChanged));
    private static void OnItemsSPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        TabbedExpander control = d as TabbedExpander;

        var old = e.OldValue as ObservableCollection<object>;

        if (old != null)
        {
            // Unsubscribe from CollectionChanged on the old collection
            old.CollectionChanged -= control.ItemsSCollectionChanged;
        }

        var n = e.NewValue as ObservableCollection<object>;

        if (n != null)
        {
            // Subscribe to CollectionChanged on the new collection
            n.CollectionChanged += control.ItemsSCollectionChanged;
        }
    }
    private void ItemsSCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Reset) base.Items.Clear();

        if (e.NewItems != null)
        {
            foreach (object item in e.NewItems)
            {
                base.Items.Add(item);
            }
        }

        if (e.OldItems != null)
        {
            foreach (object item in e.OldItems)
            {
                base.Items.RemoveAt(e.OldStartingIndex);
            }
        }
    }


    #region public
    public bool IsExpanded
    {
        get { return (bool)GetValue(IsExpandedProperty); }
        set { SetValue(IsExpandedProperty, value); }
    }
    public double EXPANDER_OFFSET
    {
        get { return (double)GetValue(EXPANDER_OFFSETProperty); }
        set { SetValue(EXPANDER_OFFSETProperty, value); }
    }
    public int EXPANDER_MIN_HEIGHT
    {
        get { return (int)GetValue(EXPANDER_MIN_HEIGHTProperty); }
        set { SetValue(EXPANDER_MIN_HEIGHTProperty, value); }
    }
    public int EXPANDER_MAX_HEIGHT
    {
        get { return (int)GetValue(EXPANDER_MAX_HEIGHTProperty); }
        set { SetValue(EXPANDER_MAX_HEIGHTProperty, value); }
    }
    public int EXPANDER_NOTEXPANDED_HEIGHT
    {
        get { return (int)GetValue(EXPANDER_NOTEXPANDED_HEIGHTProperty); }
        set { SetValue(EXPANDER_NOTEXPANDED_HEIGHTProperty, value); }
    }
    public double EXPANDER_EXPANDED_HEIGHT
    {
        get { return (double)GetValue(EXPANDER_EXPANDED_HEIGHTProperty); }
        set { SetValue(EXPANDER_EXPANDED_HEIGHTProperty, value); }
    }
    #endregion

    #region static
    public static readonly DependencyProperty IsExpandedProperty =
        DependencyProperty.Register("IsExpanded",
            typeof(bool),
            typeof(TabbedExpander),
            new PropertyMetadata(false, OnIsExpandedChanged));
    private static void OnIsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        TabbedExpander control = d as TabbedExpander;

        if ((bool)e.NewValue) control.Height = control.EXPANDER_EXPANDED_HEIGHT;
        else control.Height = control.EXPANDER_NOTEXPANDED_HEIGHT;
    }

    public static readonly DependencyProperty EXPANDER_OFFSETProperty =
        DependencyProperty.Register("EXPANDER_OFFSET",
            typeof(double),
            typeof(TabbedExpander),
            new PropertyMetadata(102d, OnOffsetChanged));
    private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public static readonly DependencyProperty EXPANDER_MIN_HEIGHTProperty =
        DependencyProperty.Register("EXPANDER_MIN_HEIGHT",
            typeof(int),
            typeof(TabbedExpander),
            new PropertyMetadata(96, OnMinHeightChanged));
    private static void OnMinHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public static readonly DependencyProperty EXPANDER_MAX_HEIGHTProperty =
        DependencyProperty.Register("EXPANDER_MAX_HEIGHT",
            typeof(int),
            typeof(TabbedExpander),
            new PropertyMetadata(400, OnMaxHeightChanged));
    private static void OnMaxHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public static readonly DependencyProperty EXPANDER_NOTEXPANDED_HEIGHTProperty =
        DependencyProperty.Register("EXPANDER_NOTEXPANDED_HEIGHT",
            typeof(int),
            typeof(TabbedExpander),
            new PropertyMetadata(30, OnNotExpandedHeightChanged));
    private static void OnNotExpandedHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public static readonly DependencyProperty EXPANDER_EXPANDED_HEIGHTProperty =
        DependencyProperty.Register("EXPANDER_EXPANDED_HEIGHT",
            typeof(double),
            typeof(TabbedExpander),
            new PropertyMetadata(100d, OnExpandedHeightChanged));
    private static void OnExpandedHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }
    #endregion
    #endregion

    #region PropertyChanged
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected void NotifyPropChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        this.Height = this.EXPANDER_NOTEXPANDED_HEIGHT;
    }
}

还有一点 MainWindow.xaml 用于测试:

<Window x:Class="PRUEBA_TABBEDEXPANDER.MainWindow"
    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:local="clr-namespace:PRUEBA_TABBEDEXPANDER"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <local:TabbedExpander TabStripPlacement="Top">
            <TabItem Header="prueba1">
                <TextBox Text="prueba1"/>
            </TabItem>
    </local:TabbedExpander>
</Grid>

这是我最后一次尝试,我的第一次尝试是将 TabbedExpanderTabControlStyle 样式直接应用于自定义控件,只是更改 TargetType,然后我尝试不添加 ItemsSProperty , 只需使用 TabControl 的继承 ItemsSource 和 'MainWindow.xaml` 中相同的语法,并将语法更改为:

<Grid>
    <local:TabbedExpander TabStripPlacement="Top">
        <local:TabbedExpander.ItemsS>
            <TabItem Header="prueba1">
                <TextBox Text="prueba1"/>
            </TabItem>
        </local:TabbedExpander.ItemsS>
    </local:TabbedExpander>
</Grid>

以及 <local:TabbedExpander.Items> 尝试使用 TabControl 继承的 Items

唯一可行的方法是将 Generic.xaml 合并为 ResourceDictionary 并将 TabbedExpanderTabControlStyle 直接应用于自定义控件...我认为这不是正确的方法做对了?因为我必须将样式应用到我要添加到我的应用程序中的每个自定义控件...

我想要么我在应用样式或使用 tabitems 集合时做错了,但现在我没有想法:(
有什么想法吗?

通常您会像这样将通用样式添加到 window(或其他高级控件)资源字典中:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Themes/Generic.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

这样所有的子控件都会继承它。你试过了吗?

请注意,现在您已经创建了一个自定义控件,您需要更改样式的目标类型,它将应用于应用程序中控件的所有实例。 (虽然看起来你已经想到了)

如果您在其他地方使用此控件,可能值得在它自己的文件中包含样式,然后其他项目可以将它包含在自己的 generic/windows 资源目录中。这就是像 MahhApps.Metro 这样的知名库的工作方式,例如使用它们的控件,我的 generic.xaml 可能看起来像这样:

<ResourceDictionary ...>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
        ... other imported resource dictionaries
    </ResourceDictionary.MergedDictionaries>
   ...my application specific stuff...
</ResourceDictionary>