如何在WPF中设置子类组件的样式

时间:2013-06-25 08:57:42

标签: wpf xaml styles attachedbehaviors

我在WPF / XAML中有一个TextBox的子类,我希望对它应用与我所有其他TextBox实例相同的样式。我定义了以下样式

<Style TargetType="TextBox" x:Key="basicTextBox" >
    <Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
    <Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
    <Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>
</Style>

和一个TextBoxBehaviours类来实现这些

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Reactive.Linq;

namespace Weingartner.Controls
{
    public static class TextBoxBehaviours
    {

        static TextBoxBehaviours()
        {}

        #region Binding Support
        private static Dictionary<Tuple<TextBox,string>, IDisposable> Bindings 
            = new Dictionary<Tuple<TextBox,string>, IDisposable>();

        private static void Bind(TextBox tb, string key, IDisposable d)
        {
            Bindings[Tuple.Create(tb, key)] = d;
        }

        private static void UnBind(TextBox tb, string key)
        {
            var t = Tuple.Create(tb, key);
            if (Bindings.ContainsKey(t))
            {
                var d = Bindings[t];
                Bindings.Remove(t);
                d.Dispose();
            }
        }
        #endregion

        static UIPropertyMetadata CreateMeta<T>(bool defaultValue, Action<T,DependencyPropertyChangedEventArgs> fn)
        where T : DependencyObject
        {
            return new UIPropertyMetadata(defaultValue, (o, e) =>{
                var t = o as T;
                if (t == null)
                {
                    return;
                }
                fn(t, e);
            });
        }

        #region Update When Enter Pressed
        public static readonly DependencyProperty 
            UpdateWhenEnterPressedProperty = 
            DependencyProperty.RegisterAttached
            ( "UpdateWhenEnterPressed"
            , typeof(bool)
            , typeof(TextBoxBehaviours)
            , CreateMeta<TextBox>(false, SetupUpdateOnEnterPressed));

        public static void 
            SetUpdateWhenEnterPressed
            ( TextBox dp
            , bool value)
        {
            dp.SetValue(UpdateWhenEnterPressedProperty, value);
        }

        public static bool 
            GetUpdateWhenEnterPressed
            ( TextBox dp)
        {
            return (bool)dp.GetValue(UpdateWhenEnterPressedProperty);
        }

        private static void 
            SetupUpdateOnEnterPressed
            ( TextBox element
            , DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                var sub = element
                    .PreviewKeyDownObserver()
                    .Where(x=>x.EventArgs.Key==Key.Enter)
                    .Subscribe(x=>DoUpdateSource(element));

                Bind(element, "KeyEnter", sub);
            }else{
                UnBind(element, "KeyEnter");
            }

        }

        static void DoUpdateSource(TextBox source)
        {
            BindingExpression binding = BindingOperations.GetBindingExpression(source, TextBox.TextProperty);

            if (binding != null)
            {
                binding.UpdateSource();
            }
        }
        #endregion
        #region SelectAll When Enter Pressed
        public static readonly DependencyProperty 
            SelectAllWhenEnterPressedProperty = 
            DependencyProperty.RegisterAttached
            ( "SelectAllWhenEnterPressed"
            , typeof(bool)
            , typeof(TextBoxBehaviours)
            , CreateMeta<TextBox>(false, SetupSelectAllOnEnterPressed));

        public static void 
            SetSelectAllWhenEnterPressed
            ( TextBox dp
            , bool value)
        {
            dp.SetValue(SelectAllWhenEnterPressedProperty, value);
        }

        public static bool 
            GetSelectAllWhenEnterPressed
            ( TextBox dp)
        {
            return (bool)dp.GetValue(SelectAllWhenEnterPressedProperty);
        }

        private static void 
            SetupSelectAllOnEnterPressed
            ( TextBox element
            , DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                var sub = element
                    .PreviewKeyDownObserver()
                    .Where(x=>x.EventArgs.Key==Key.Enter)
                    .Subscribe(x=>element.SelectAll());

                Bind(element, "KeyEnterSelectAll", sub);
            }else{
                UnBind(element, "KeyEnterSelectAll");
            }

        }

        #endregion
        #region Select All On Focus
        public static readonly DependencyProperty 
            SelectAllOnFocusProperty = 
            DependencyProperty.RegisterAttached
            ( "SelectAllOnFocus"
            , typeof(bool)
            , typeof(TextBoxBehaviours)
            , CreateMeta<TextBox>(false, SetupSelectAllOnFocus));


        public static void 
            SetSelectAllOnFocus
            ( TextBox dp
            , bool value)
        {
            dp.SetValue(SelectAllOnFocusProperty, value);
        }

        public static bool 
            GetSelectAllOnFocus
            ( TextBox dp)
        {
            return (bool)dp.GetValue(SelectAllOnFocusProperty);
        }

        private static void 
            SetupSelectAllOnFocus
            ( TextBox element
            , DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                Bind(element, "Focus", element.GotFocusObserver().Subscribe(x => element.SelectAll()));
            }else{
                UnBind(element, "Focus");
            }
        }
        #endregion

    }
}

在我一直在做的XAML文件中

<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>

以及所有后面的TextBoxes获得我想要的behviour。鉴于我的子类也是一个TextBox类,我认为这些也会得到行为,但事实并非如此。我尝试通过扩展样式来明确

<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>

<Style TargetType="Controls:EditForLength">
    <Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
    <Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
    <Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>

</Style>

但是我收到错误,告诉我EditForLength

的关键字
System.ArgumentException occurred
  HResult=-2147024809
  Message=Item has already been added. Key in dictionary: 'Weingartner.Controls.EditForLength'  Key being added: 'Weingartner.Controls.EditForLength'
  Source=mscorlib
  StackTrace:
       at System.Collections.Hashtable.Insert(Object key, Object nvalue, Boolean add)
  InnerException: 

因此,在通过父类的样式安装的摘要行为中,不会传播给子类实例,但是当尝试添加另一种针对子类的样式时,会出现一些字典错误。

注意我已经尝试过对EditForLength控件实例的行为明确表示他们正常工作。我无法通过样式安装行为。

更新

发现即使所有重要的位都出现错误 除了样式声明被注释掉

        <!--<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>-->

        <Style TargetType="Controls:EditForLength">
            <!--
            <Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
            <Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
            <Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>
             -->

        </Style>

1 个答案:

答案 0 :(得分:0)

关于异常:您似乎已经在字典(或其合并的字典)中添加了此样式 - 尝试搜索它,我相信您会找到具有相同密钥的样式。

关于隐式样式:它们不适用于目标类型的子类(尝试想象它们是否应用 - 如果您为FrameworkElement创建了隐式样式 - 大部分控件的布局将被破坏)