Як додати змішану поведінку у програмі встановлення стилів


88

Я створив поведінку Blend для кнопки. Як я можу встановити це для всіх моїх кнопок у додатку.

<Button ...>
  <i:Interaction.Behaviors>
    <local:MyBehavior />
  </i:Interaction.Behaviors>
</Button>

Однак, коли я намагаюся:

<Style>
  <Setter Property="i:Interaction.Behaviors">
    <Setter.Value>
      <local:MyBehavior />
    </Setter.Value>
  </Setter>
</Style>

Я отримую помилку

Властивість "Поведінки" не має доступного налаштування.

Відповіді:


76

У мене була та сама проблема, і я придумав рішення. Я знайшов це питання після того, як вирішив його, і бачу, що моє рішення має багато спільного з Марковим. Однак цей підхід дещо інший.

Основна проблема полягає в тому, що поведінка та тригери асоціюються з певним об'єктом, і тому ви не можете використовувати один і той же екземпляр поведінки для кількох різних асоційованих об'єктів. Коли ви визначаєте свою поведінку, вбудований XAML застосовує цей взаємозв'язок один до одного. Однак при спробі встановити поведінку в стилі стиль можна повторно використовувати для всіх об'єктів, до яких він застосовується, і це призведе до винятків у базових класах поведінки. Насправді автори доклали чимало зусиль, щоб не дати нам навіть спробувати це зробити, знаючи, що це не спрацює.

Перша проблема полягає в тому, що ми навіть не можемо сконструювати значення параметра встановлення поведінки, оскільки конструктор є внутрішнім. Отже, нам потрібна наша власна поведінка та активація класів колекції.

Наступна проблема полягає в тому, що властивості поведінки та прикріплених тригерів не мають установок, і тому їх можна додавати лише за допомогою вбудованого XAML. Цю проблему ми вирішуємо за допомогою власних прикріплених властивостей, які маніпулюють первинною поведінкою та властивостями тригера.

Третя проблема полягає в тому, що наша колекція поведінки корисна лише для однієї цільової стилістики. Це ми вирішуємо, використовуючи мало використовувану функцію XAML, x:Shared="False"яка створює нову копію ресурсу кожного разу, коли на нього посилається.

Нарешті, проблема полягає в тому, що поведінка та тригери не схожі на інші встановлення стилів; ми не хочемо замінювати стару поведінку новою поведінкою, оскільки вони могли робити дико різні речі. Отже, якщо ми визнаємо, що як тільки ви додасте поведінку, ви не можете її забрати (і саме так зараз працює поведінка), ми можемо зробити висновок, що поведінка та тригери повинні бути адитивними, і це може бути оброблено нашими прикріпленими властивостями.

Ось зразок із використанням цього підходу:

<Grid>
    <Grid.Resources>
        <sys:String x:Key="stringResource1">stringResource1</sys:String>
        <local:Triggers x:Key="debugTriggers" x:Shared="False">
            <i:EventTrigger EventName="MouseLeftButtonDown">
                <local:DebugAction Message="DataContext: {0}" MessageParameter="{Binding}"/>
                <local:DebugAction Message="ElementName: {0}" MessageParameter="{Binding Text, ElementName=textBlock2}"/>
                <local:DebugAction Message="Mentor: {0}" MessageParameter="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"/>
            </i:EventTrigger>
        </local:Triggers>
        <Style x:Key="debugBehavior" TargetType="FrameworkElement">
            <Setter Property="local:SupplementaryInteraction.Triggers" Value="{StaticResource debugTriggers}"/>
        </Style>
    </Grid.Resources>
    <StackPanel DataContext="{StaticResource stringResource1}">
        <TextBlock Name="textBlock1" Text="textBlock1" Style="{StaticResource debugBehavior}"/>
        <TextBlock Name="textBlock2" Text="textBlock2" Style="{StaticResource debugBehavior}"/>
        <TextBlock Name="textBlock3" Text="textBlock3" Style="{StaticResource debugBehavior}"/>
    </StackPanel>
</Grid>

У прикладі використовуються тригери, але поведінка працює однаково. У прикладі ми показуємо:

  • стиль можна застосувати до декількох текстових блоків
  • кілька типів прив'язки даних працюють правильно
  • дія налагодження, яка генерує текст у вікні виводу

Ось приклад поведінки, наш DebugAction. Більш правильно це дія, але через зловживання мовою ми називаємо поведінку, тригери та дії "поведінкою".

public class DebugAction : TriggerAction<DependencyObject>
{
    public string Message
    {
        get { return (string)GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public static readonly DependencyProperty MessageProperty =
        DependencyProperty.Register("Message", typeof(string), typeof(DebugAction), new UIPropertyMetadata(""));

    public object MessageParameter
    {
        get { return (object)GetValue(MessageParameterProperty); }
        set { SetValue(MessageParameterProperty, value); }
    }

    public static readonly DependencyProperty MessageParameterProperty =
        DependencyProperty.Register("MessageParameter", typeof(object), typeof(DebugAction), new UIPropertyMetadata(null));

    protected override void Invoke(object parameter)
    {
        Debug.WriteLine(Message, MessageParameter, AssociatedObject, parameter);
    }
}

Нарешті, наші колекції та додані властивості, щоб все це працювало. За аналогією з Interaction.Behaviorsвластивістю, на яку ви націлюєтесь, викликається, SupplementaryInteraction.Behaviorsоскільки, встановивши цю властивість, ви додасте поведінку до Interaction.Behaviorsтригерів, а також до них.

public class Behaviors : List<Behavior>
{
}

public class Triggers : List<TriggerBase>
{
}

public static class SupplementaryInteraction
{
    public static Behaviors GetBehaviors(DependencyObject obj)
    {
        return (Behaviors)obj.GetValue(BehaviorsProperty);
    }

    public static void SetBehaviors(DependencyObject obj, Behaviors value)
    {
        obj.SetValue(BehaviorsProperty, value);
    }

    public static readonly DependencyProperty BehaviorsProperty =
        DependencyProperty.RegisterAttached("Behaviors", typeof(Behaviors), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyBehaviorsChanged));

    private static void OnPropertyBehaviorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = Interaction.GetBehaviors(d);
        foreach (var behavior in e.NewValue as Behaviors) behaviors.Add(behavior);
    }

    public static Triggers GetTriggers(DependencyObject obj)
    {
        return (Triggers)obj.GetValue(TriggersProperty);
    }

    public static void SetTriggers(DependencyObject obj, Triggers value)
    {
        obj.SetValue(TriggersProperty, value);
    }

    public static readonly DependencyProperty TriggersProperty =
        DependencyProperty.RegisterAttached("Triggers", typeof(Triggers), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyTriggersChanged));

    private static void OnPropertyTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var triggers = Interaction.GetTriggers(d);
        foreach (var trigger in e.NewValue as Triggers) triggers.Add(trigger);
    }
}

і ось у вас це, повнофункціональна поведінка та тригери, застосовані через стилі.


Чудовий матеріал, це чудово працює. Я помітив, що якщо ви помістите стиль, наприклад, у ресурси UserControl, тоді e.NewValue може спочатку бути нульовим (може залежати від використовуваного елемента керування - я використовую це на XamDataTreeNodeControl в Infragistics XamDataTree). Тож я додав невелику перевірку розумності в OnPropertyTriggersChanged: if (e.NewValue! = Null)
MetalMikester

Хтось мав проблеми з цим підходом під час застосування сеттера в неявному стилі? Я змусив його добре працювати з неявним стилем (такий із ключем), але я отримую циклічне виняток посилання, якщо це в неявному стилі.
Джейсон Френк,

1
Гарне рішення, але, на жаль, воно не працює в WinRT, оскільки x: Shared не існує на цій платформі ...
Thomas Levesque

1
Я можу підтвердити, що це рішення працює. Щиро дякую, що поділилися нею. Однак я ще не пробував це з неявним стилем.
Golvellius

2
@ Джейсон Френк, Дякую, як посилання для інших ... Я зробив це в обох випадках: неявним і явним. Насправді я задаю питання, куди б я поклав весь свій код, щоб допомогти іншим, але хтось вважає, що моє запитання було дублікатом. Я не можу відповісти на власне запитання, даючи все, що знайшов. Думаю, я виявляю досить приємні речі. :-( ... Я сподіваюся, це трапляється не надто часто, тому що така поведінка позбавляє інших користувачів корисної інформації.
Ерік Уеле

27

Підсумовуючи відповіді та цю чудову статтю « Змішайте поведінку в стилях» , я прийшов до цього загального короткого та зручного рішення:

Я створив загальний клас, який можна успадкувати за будь-якою поведінкою.

public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
        where TComponent : System.Windows.DependencyObject
        where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior> , new ()
    {
        public static DependencyProperty IsEnabledForStyleProperty =
            DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool),
            typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged)); 

        public bool IsEnabledForStyle
        {
            get { return (bool)GetValue(IsEnabledForStyleProperty); }
            set { SetValue(IsEnabledForStyleProperty, value); }
        }

        private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement uie = d as UIElement;

            if (uie != null)
            {
                var behColl = Interaction.GetBehaviors(uie);
                var existingBehavior = behColl.FirstOrDefault(b => b.GetType() ==
                      typeof(TBehavior)) as TBehavior;

                if ((bool)e.NewValue == false && existingBehavior != null)
                {
                    behColl.Remove(existingBehavior);
                }

                else if ((bool)e.NewValue == true && existingBehavior == null)
                {
                    behColl.Add(new TBehavior());
                }    
            }
        }
    }

Отже, ви можете просто повторно використовувати його з безліччю таких компонентів:

public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour>
    { ... }

І в XAML достатньо, щоб оголосити:

 <Style TargetType="ComboBox">
            <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>

Отже, в основному клас AttachableForStyleBehavior робив речі xaml, реєструючи екземпляр поведінки для кожного компонента в стилі. Детальніше див. За посиланням.


Працює як шарм! За допомогою комбінованої поведінки Scrolling я позбувся Inner RowDetailsTemplate-Datagrids, які не прокручують батьківські Datagrids.
Філіп Міхальскі

Радий допомогти, насолоджуватися =)
Рома Бородов

1
а як щодо прив’язки даних із властивостями залежностей у поведінці?
JobaDiniz

Я не знаю, як особисто зв’язатися з користувачем або відмовити редагуванню з негативними відгуками. Тож шановний @Der_Meister та інші редактори, будь ласка, уважно прочитайте код, перш ніж намагатись його редагувати. Це може вплинути на інших користувачів і на мою репутацію. У цьому випадку, видаляючи властивість IsEnabledForStyle і наполегливо замінюючи його статичними методами, ви знищуєте можливість прив'язки до нього в xaml, що є основним пунктом цього питання. Тож виглядає так, що ви не читали код до кінця. На жаль, я не можу відхилити ваше редагування з великим мінусом, тому, будь ласка, будьте обережні надалі.
Рома Бородов

1
@RomaBorodov, все працює в XAML. Це правильний спосіб визначити прикріплене властивість (яке відрізняється від властивості залежності). Див. Документацію: docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/…
Der_Meister,

19

1.Створення вкладеного майна

public static class DataGridCellAttachedProperties
{
    //Register new attached property
    public static readonly DependencyProperty IsSingleClickEditModeProperty =
        DependencyProperty.RegisterAttached("IsSingleClickEditMode", typeof(bool), typeof(DataGridCellAttachedProperties), new UIPropertyMetadata(false, OnPropertyIsSingleClickEditModeChanged));

    private static void OnPropertyIsSingleClickEditModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGridCell = d as DataGridCell;
        if (dataGridCell == null)
            return;

        var isSingleEditMode = GetIsSingleClickEditMode(d);
        var behaviors =  Interaction.GetBehaviors(d);
        var singleClickEditBehavior = behaviors.SingleOrDefault(x => x is SingleClickEditDataGridCellBehavior);

        if (singleClickEditBehavior != null && !isSingleEditMode)
            behaviors.Remove(singleClickEditBehavior);
        else if (singleClickEditBehavior == null && isSingleEditMode)
        {
            singleClickEditBehavior = new SingleClickEditDataGridCellBehavior();
            behaviors.Add(singleClickEditBehavior);
        }
    }

    public static bool GetIsSingleClickEditMode(DependencyObject obj)
    {
        return (bool) obj.GetValue(IsSingleClickEditModeProperty);
    }

    public static void SetIsSingleClickEditMode(DependencyObject obj, bool value)
    {
        obj.SetValue(IsSingleClickEditModeProperty, value);
    }
}

2.Створіть поведінку

public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>
        {
            protected override void OnAttached()
            {
                base.OnAttached();
                AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown;
            }

            protected override void OnDetaching()
            {
                base.OnDetaching();
                AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown;
            }

            void DataGridCellPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                 DataGridCell cell = sender as DataGridCell;
                if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
                {
                    if (!cell.IsFocused)
                    {
                        cell.Focus();
                    }
                    DataGrid dataGrid = LogicalTreeWalker.FindParentOfType<DataGrid>(cell); //FindVisualParent<DataGrid>(cell);
                    if (dataGrid != null)
                    {
                        if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                        {
                            if (!cell.IsSelected)
                                cell.IsSelected = true;
                        }
                        else
                        {
                            DataGridRow row =  LogicalTreeWalker.FindParentOfType<DataGridRow>(cell); //FindVisualParent<DataGridRow>(cell);
                            if (row != null && !row.IsSelected)
                            {
                                row.IsSelected = true;
                            }
                        }
                    }
                }
            }    
        }

3. Створіть стиль і встановіть прикріплену властивість

        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/>
        </Style>

Коли я намагаюся отримати доступ до властивості DependencyProperty зі стилю, він каже, що IsSingleClickEditMode не розпізнаний або недоступний?
Ігор Месарош,

Вибачте, погано .. як тільки я прокоментував, я зрозумів, що GetIsSingleClickEditMode повинен відповідати рядку, який ви передаєте в DependencyProperty.RegisterAttached
Ігор Месарош 02

OnDetaching додає ще один обробник подій, це слід виправити (не можна змінювати жодного символу під час редагування публікації ...)
BalintPogatsa

11

У мене є інша ідея, щоб уникнути створення прикріпленого властивості для будь-якої поведінки:

  1. Інтерфейс творця поведінки:

    public interface IBehaviorCreator
    {
        Behavior Create();
    }
    
  2. Невелика колекція помічників:

    public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
    
  3. Клас-помічник, який надає поведінку:

    public static class BehaviorInStyleAttacher
    {
        #region Attached Properties
    
        public static readonly DependencyProperty BehaviorsProperty =
            DependencyProperty.RegisterAttached(
                "Behaviors",
                typeof(BehaviorCreatorCollection),
                typeof(BehaviorInStyleAttacher),
                new UIPropertyMetadata(null, OnBehaviorsChanged));
    
        #endregion
    
        #region Getter and Setter of Attached Properties
    
        public static BehaviorCreatorCollection GetBehaviors(TreeView treeView)
        {
            return (BehaviorCreatorCollection)treeView.GetValue(BehaviorsProperty);
        }
    
        public static void SetBehaviors(
            TreeView treeView, BehaviorCreatorCollection value)
        {
            treeView.SetValue(BehaviorsProperty, value);
        }
    
        #endregion
    
        #region on property changed methods
    
        private static void OnBehaviorsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is BehaviorCreatorCollection == false)
                return;
    
            BehaviorCreatorCollection newBehaviorCollection = e.NewValue as BehaviorCreatorCollection;
    
            BehaviorCollection behaviorCollection = Interaction.GetBehaviors(depObj);
            behaviorCollection.Clear();
            foreach (IBehaviorCreator behavior in newBehaviorCollection)
            {
                behaviorCollection.Add(behavior.Create());
            }
        }
    
        #endregion
    }
    
  4. Тепер ваша поведінка, яка реалізує IBehaviorCreator:

    public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>, IBehaviorCreator
    {
        //some code ...
    
        public Behavior Create()
        {
            // here of course you can also set properties if required
            return new SingleClickEditDataGridCellBehavior();
        }
    }
    
  5. А тепер використовуйте його в xaml:

    <Style TargetType="{x:Type DataGridCell}">
      <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" >
        <Setter.Value>
          <helper:BehaviorCreatorCollection>
            <behaviors:SingleClickEditDataGridCellBehavior/>
          </helper:BehaviorCreatorCollection>
        </Setter.Value>
      </Setter>
    </Style>
    

5

Я не зміг знайти оригінальну статтю, але зміг відтворити ефект.

#region Attached Properties Boilerplate

    public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(ScrollIntoViewBehavior), new PropertyMetadata(false, OnIsActiveChanged));

    public static bool GetIsActive(FrameworkElement control)
    {
        return (bool)control.GetValue(IsActiveProperty);
    }

    public static void SetIsActive(
      FrameworkElement control, bool value)
    {
        control.SetValue(IsActiveProperty, value);
    }

    private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = Interaction.GetBehaviors(d);
        var newValue = (bool)e.NewValue;

        if (newValue)
        {
            //add the behavior if we don't already have one
            if (!behaviors.OfType<ScrollIntoViewBehavior>().Any())
            {
                behaviors.Add(new ScrollIntoViewBehavior());
            }
        }
        else
        {
            //remove any instance of the behavior. (There should only be one, but just in case.)
            foreach (var item in behaviors.ToArray())
            {
                if (item is ScrollIntoViewBehavior)
                    behaviors.Remove(item);
            }
        }
    }


    #endregion
<Style TargetType="Button">
    <Setter Property="Blah:ScrollIntoViewBehavior.IsActive" Value="True" />
</Style>

Доводиться писати це для кожної поведінки - це трохи ПІТА.
Стівен Дрю

0

Код поведінки передбачає Visual, тому ми можемо додавати його лише у Visual. Тож єдиний варіант, який я міг бачити, - це додати до одного з елементів всередині ControlTemplate, щоб отримати поведінку, додану до Стилю, і вплинути на всі екземпляри певного елемента управління.


0

Стаття Вступ до прикріплених форм поведінки в WPF реалізує прикріплену поведінку, використовуючи лише стиль, і може також бути пов’язаною чи корисною.

Метод у статті "Вступ до поведінки, що додається" повністю уникає тегів Інтерактивності, використовуючи стиль. Я не знаю, чи це просто тому, що це більш застаріла техніка, чи, якщо це все-таки надає певні переваги там, де в деяких сценаріях слід віддавати перевагу.


2
Це не поведінка Blend, це "поведінка" за допомогою простої прикріпленої властивості.
Стівен Дрю

0

Мені подобається підхід, показаний відповідями Романа Двоскіна та Джонатана Аллена в цій темі. Коли я вперше вивчав цю техніку, я отримав користь від цієї публікації в блозі, яка містить більше пояснень щодо цієї техніки. А щоб побачити все в контексті, ось весь вихідний код класу, про який автор розповідає у своєму дописі в блозі.


0

Оголосіть індивідуальну поведінку / тригер як ресурси:

<Window.Resources>

    <i:EventTrigger x:Key="ET1" EventName="Click">
        <ei:ChangePropertyAction PropertyName="Background">
            <ei:ChangePropertyAction.Value>
                <SolidColorBrush Color="#FFDAD32D"/>
            </ei:ChangePropertyAction.Value>
        </ei:ChangePropertyAction>
    </i:EventTrigger>

</Window.Resources>

Вставте їх до колекції:

<Button x:Name="Btn1" Content="Button">

        <i:Interaction.Triggers>
             <StaticResourceExtension ResourceKey="ET1"/>
        </i:Interaction.Triggers>

</Button>

4
Як це відповідає OP? Тригер не додається через стиль у вашій відповіді.
Kryptos

0

На основі цієї відповіді я зробив більш просте рішення, потрібен лише один клас, і немає необхідності застосовувати щось інше у своїй поведінці.

public static class BehaviorInStyleAttacher
{
    #region Attached Properties

    public static readonly DependencyProperty BehaviorsProperty =
        DependencyProperty.RegisterAttached(
            "Behaviors",
            typeof(IEnumerable),
            typeof(BehaviorInStyleAttacher),
            new UIPropertyMetadata(null, OnBehaviorsChanged));

    #endregion

    #region Getter and Setter of Attached Properties

    public static IEnumerable GetBehaviors(DependencyObject dependencyObject)
    {
        return (IEnumerable)dependencyObject.GetValue(BehaviorsProperty);
    }

    public static void SetBehaviors(
        DependencyObject dependencyObject, IEnumerable value)
    {
        dependencyObject.SetValue(BehaviorsProperty, value);
    }

    #endregion

    #region on property changed methods

    private static void OnBehaviorsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is IEnumerable == false)
            return;

        var newBehaviorCollection = e.NewValue as IEnumerable;

        BehaviorCollection behaviorCollection = Interaction.GetBehaviors(depObj);
        behaviorCollection.Clear();
        foreach (Behavior behavior in newBehaviorCollection)
        {
            // you need to make a copy of behavior in order to attach it to several controls
            var copy = behavior.Clone() as Behavior;
            behaviorCollection.Add(copy);
        }
    }

    #endregion
}

а використання зразка -

<Style TargetType="telerik:RadComboBox" x:Key="MultiPeriodSelectableRadComboBox">
    <Setter Property="AllowMultipleSelection" Value="True" />
    <Setter Property="behaviors:BehaviorInStyleAttacher.Behaviors">
        <Setter.Value>
            <collections:ArrayList>
                <behaviors:MultiSelectRadComboBoxBehavior
                        SelectedItems="{Binding SelectedPeriods}"
                        DelayUpdateUntilDropDownClosed="True"
                        SortSelection="True" 
                        ReverseSort="True" />
            </collections:ArrayList>
        </Setter.Value>
    </Setter>
</Style>

Не забудьте додати цей xmlns, щоб використовувати ArrayList:

xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.