自定义控件x:类问题和动画

时间:2017-02-15 18:22:02

标签: c# wpf xaml custom-controls

我正在尝试创建一个包含一些动画的菜单自定义控件,以使事物看起来更“流畅”。这是我遇到问题的地方,我希望我的自定义控件的宽度由动画驱动,以模拟菜单的扩展。我可以通过在模板中使用触发器来实现这一点但是使用这种方法我似乎无法找到绑定“to”的方法,我已经读过因为线程安全而不允许这样做所以我想我会使用“Click”事件并在Code Behind中执行动画。

当我尝试将我的类添加到资源字典时,我最终得到一堆错误,说明'“DefaultStyleKeyProperty”在类文件的当前上下文“中不存在。我需要将我的类添加到资源字典中,以便能够访问任何UI对象。所以我对如何实现这一点感到有些困惑,或者我是以错误的方式解决这个问题?

这是我到目前为止所做的:

NavMenu.xaml

<ResourceDictionary x:Class="Happ.UI.Controls.NavMenu"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Happ.UI.Controls">

    <Style TargetType="{x:Type local:NavMenu}">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <StackPanel Background="Yellow" Orientation="Vertical"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:NavMenu}">
                        <Border Background="{TemplateBinding Background}" BorderBrush="Transparent">
                        <ToggleButton x:Name="btnMenu" 
                                      Margin="10" Padding="4" Background="Transparent"
                                      HorizontalAlignment="Left" VerticalAlignment="Top"
                                      IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IsExpanded}" />
                        </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

NavMenu.cs

namespace Happ.UI.Controls {

    public partial class NavMenu : Menu {

        static NavMenu() {
            DefaultStyleKeyProperty.OverrideMetadata( typeof( NavMenu ), new FrameworkPropertyMetadata( typeof( NavMenu ) ) );
        }

        public bool IsExpanded {
            get {
                return (bool)GetValue( IsExpandedProperty );
            }
            set {
                TimeSpan tsTime = new TimeSpan( 0, 0, 0, 0, 500 );
                DoubleAnimation mnuAnim = new DoubleAnimation( MinWidth, tsTime );
                btnMenu.BeginAnimation( Width, mnuAnim );
                SetValue( IsExpandedProperty, value );
            }
        }

        public static readonly DependencyProperty IsExpandedProperty =
             DependencyProperty.Register( "IsExpanded", typeof( bool ), typeof( NavMenu ), new PropertyMetadata( true ) );
        }
    }
}

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:Happ.UI.Controls">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/Happ.UI.Controls;component/Templates/NavMenu.xaml" />
    </ResourceDictionary.MergedDictionaries>



</ResourceDictionary>

我尝试将xClass添加到Generic.xaml中也没有运气,当我这样做时,我又收到了另一个错误:“'NavMenu'的部分声明必须没有指定不同的基类”。

修改

我知道必须有一个更好的方法,使用交互库。我最终做的是创建一个专门为动画创建的新类。然后我可以将新的TriggerAction附加到我的Control,我确实想到的一件事是我需要为目标动画元素设置ListContainer。这是因为无法实现从0到“自动”的动画,Panel提供了一种获取子项并在代码中计算“自动”宽度的方法。以下是我的更新:

NavMenu.cs

namespace Happ.UI.Controls {

    public class NavMenu : Menu {

        static NavMenu() {
            DefaultStyleKeyProperty.OverrideMetadata( typeof( NavMenu ), new FrameworkPropertyMetadata( typeof( NavMenu ) ) );
        }

        /// <summary>
        /// Flag to Tell if the Menu is Expanded
        /// </summary>
        public bool IsExpanded {
            get {
                return (bool)GetValue( IsExpandedProperty );
            }
            set {
                SetValue( IsExpandedProperty, value );
            }
        }

        public static readonly DependencyProperty IsExpandedProperty =
             DependencyProperty.Register( "IsExpanded", typeof( bool ), typeof( NavMenu ), new PropertyMetadata( true ) );
    }
}

NavMenu.Animations.cs

namespace Happ.UI.Controls {

    public class NavMenuAnimations : System.Windows.Interactivity.TriggerAction<UIElement> {

        /// <summary>
        /// Sets the Target Element for the Animation
        /// </summary>
        public Panel TargetPanel {
            get {
                return (Panel)GetValue( TargetPanelProperty );
            }
            set {
                SetValue( TargetPanelProperty, value );
            }
        }

        public static readonly DependencyProperty TargetPanelProperty =
            DependencyProperty.Register( "TargetPanel", typeof( Panel ), typeof( NavMenuAnimations ),
            new PropertyMetadata( null ) );

        /// <summary>
        /// Sets the Target Element for the Animation
        /// </summary>
        public double Seconds {
            get {
                return (double)GetValue( SecondsProperty );
            }
            set {
                SetValue( SecondsProperty, value );
            }
        }

        public static readonly DependencyProperty SecondsProperty =
            DependencyProperty.Register( "Seconds", typeof( double ), typeof( NavMenuAnimations ),
            new PropertyMetadata( (double)0.5 ) );

        /// <summary>
        /// This is the Main Animation Method that is called each time the Trigger occurs
        /// </summary>
        /// <param name="parameter"></param>
        protected override void Invoke( object parameter ) {
            double maxWidth = 0;

            //Make sure that we have a Target Element
            if( TargetPanel == null ) {
                throw new Exception( "No Target Element specified for the animation. (NavMenu Animations)" );
            }

            //Make sure the Min Width and Height are Set
            TargetPanel.MinWidth = ( Width > 0 ) ? Width : TargetPanel.MinWidth;


            //Check if the MaxWidth has been set
            if( Double.IsInfinity( TargetPanel.MaxWidth ) ) {

                //Loop through the Children and Get Width
                foreach( UIElement elem in TargetPanel.Children ) {

                    //Update the Max Width of the Panel
                    maxWidth += elem.RenderSize.Width;
                }

                //Check if we found a MaxWidth. if not return
                if( maxWidth == 0 )
                    return;

                //Assign the new MaxWidth
                TargetPanel.MaxWidth = maxWidth;
            }

            //Check if Width is at Minumum (Shrunk)
            if( TargetPanel.Width == TargetPanel.MinWidth ) {

                TargetPanel.BeginAnimation( FrameworkElement.WidthProperty, new DoubleAnimation( TargetPanel.MaxWidth, TimeSpan.FromSeconds( Seconds ) ) );
            }
            else {

                TargetPanel.BeginAnimation( FrameworkElement.WidthProperty, new DoubleAnimation( TargetPanel.MinWidth, TimeSpan.FromSeconds( Seconds ) ) );
            }


        }

    }
}

NavMenuTemplate.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                    xmlns:local="clr-namespace:Happ.UI.Controls">

    <Style TargetType="{x:Type local:NavMenu}">
        <Setter Property="Width" Value="50"/>
        <Setter Property="Padding" Value="10" />
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel x:Name="navMenu" Orientation="Vertical">

                    </StackPanel>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:NavMenu}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="Transparent"
                            Padding="{TemplateBinding Padding}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Viewbox Grid.Row="0"
                                     HorizontalAlignment="Left" VerticalAlignment="Top"
                                     Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IconSize}"
                                     Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IconSize}">
                                <ToggleButton x:Name="btnMenu" Grid.Row="0" 
                                              Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=MenuIcon}"
                                              Background="Transparent"
                                              Padding="0"
                                              IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IsExpanded}">
                                </ToggleButton>
                            </Viewbox>
                            <ItemsPresenter Grid.Row="1" MinWidth="50">
                                <i:Interaction.Triggers>
                                    <i:EventTrigger SourceName="btnMenu" EventName="Click" >
                                        <local:NavMenuAnimations TargetPanel="{Binding ElementName=navMenu}"  Technique="ExpandWidth" />
                                    </i:EventTrigger>
                                </i:Interaction.Triggers>
                            </ItemsPresenter>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

这与我需要的非常接近,我只剩下一个问题。由于我的元素的方向,我需要定义一个ItemsPanelTemplate,这是我需要Animate的元素。但问题是我无法弄清楚如何找到“navMenu”元素。我知道这只是一个上下文问题我只是不知道如何到达正确的地方。我已经尝试使用RelativeParent = {RelativeParent TemplatedParent}但我不能使用指定的名称。谁能告诉我如何将引用传递给ItemsPanelTemplate?

1 个答案:

答案 0 :(得分:0)

为什么NavMenu首先定义为partial?除了您在NavMenu.xaml文件中定义的默认模板之外,这应该只是没有任何XAML的类。您应该有任何NavMenu.xaml.cs文件。

此外,IsExpanded CLR包装器设置依赖项属性的值。

要获取对自定义控件模板中定义的“btnMenu”ToggleButton的引用,您应该覆盖OnApplyTemplate方法:

public class NavMenu : Menu
{
    static NavMenu()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NavMenu), new FrameworkPropertyMetadata(typeof(NavMenu)));
    }

    public bool IsExpanded
    {
        get
        {
            return (bool)GetValue(IsExpandedProperty);
        }
        set
        {
            SetValue(IsExpandedProperty, value);
        }
    }

    public static readonly DependencyProperty IsExpandedProperty =
         DependencyProperty.Register("IsExpanded", typeof(bool), typeof(NavMenu), new PropertyMetadata(true));

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

        System.Windows.Controls.Primitives.ToggleButton btnMenu = Template.FindName("btnMenu", this) as System.Windows.Controls.Primitives.ToggleButton;
        TimeSpan tsTime = new TimeSpan(0, 0, 0, 0, 500);
        DoubleAnimation mnuAnim = new DoubleAnimation(0.0, MinWidth, tsTime);
        btnMenu.BeginAnimation(WidthProperty, mnuAnim);
    }
}