Как добавить поведение Blend в стильном сеттере
Я создал свойство Blend для Button. Как я могу установить это на все мои кнопки в приложении.
<Button ...>
<i:Interaction.Behaviors>
<local:MyBehavior />
</i:Interaction.Behaviors>
</Button>
Однако, когда я пытаюсь:
<Style>
<Setter Property="i:Interaction.Behaviors">
<Setter.Value>
<local:MyBehavior />
</Setter.Value>
</Setter>
</Style>
Я получаю сообщение об ошибке
Свойство "Behaviors" не имеет доступного setter.
Ответы
Ответ 1
У меня была такая же проблема, и я придумал решение. Я нашел этот вопрос после того, как решил его, и вижу, что мое решение имеет много общего с Марком. Однако этот подход немного отличается.
Основная проблема заключается в том, что поведение и триггеры ассоциируются с определенным объектом, поэтому вы не можете использовать один и тот же экземпляр поведения для нескольких разных связанных объектов. Когда вы определяете свое поведение, встроенный XAML обеспечивает это взаимно-однозначное отношение. Однако, когда вы пытаетесь установить поведение в стиле, стиль может быть повторно использован для всех объектов, к которым он применяется, и это будет генерировать исключения в базовых классах поведения. На самом деле авторы прилагали значительные усилия, чтобы помешать нам даже попытаться это сделать, зная, что это не сработает.
Первая проблема заключается в том, что мы даже не можем построить значение setterter, потому что конструктор является внутренним. Поэтому нам нужно собственное поведение и триггерные классы.
Следующая проблема заключается в том, что свойства поведения и триггера не имеют сеттеров, поэтому их можно добавить только с помощью встроенного 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);
}
}
и там у вас есть это, полнофункциональное поведение и триггеры, применяемые через стили.
Ответ 2
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>
Ответ 3
Подводя ответы и эту замечательную статью Blend Behaviors в стилях, я пришел к этому родовому короткому и удобному решению:
Я сделал общий класс, который может быть унаследован любым поведением.
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, зарегистрировав экземпляр поведения для каждого компонента в стиле. Для получения дополнительной информации см. Ссылку.
Ответ 4
У меня есть другая идея, чтобы избежать создания прикрепленного свойства для каждого поведения:
-
Интерфейс создателя поведения:
public interface IBehaviorCreator
{
Behavior Create();
}
-
Небольшая сборка:
public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
-
Класс помощника, который придает поведение:
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
}
-
Теперь ваше поведение, которое реализует 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();
}
}
-
И теперь используйте его в 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>
Ответ 6
Объявить индивидуальное поведение/триггер как ресурсы:
<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>
Ответ 7
Код поведения ожидает Visual, поэтому мы можем добавить его только на визуальный. Поэтому единственным вариантом, который я мог видеть, является добавление к одному из элементов внутри ControlTemplate, чтобы получить добавление поведения в стиль и повлиять на все экземпляры определенного элемента управления.
Ответ 8
В статье Введение в привязное поведение в WPF реализуется связанное поведение только с использованием стиля, а также может быть связано или полезно.
В статье "Введение в привязное поведение" техника избегает тегов интерактивности, используя стиль. Я не знаю, является ли это только потому, что это более датированная методика, или, если это еще приносит некоторые преимущества, когда в некоторых сценариях это следует предпочесть.
Ответ 9
Мне нравится подход, показанный в ответах Романа Двоскина и Джонатана Аллена в этой теме. Когда я впервые изучил эту технику, я получил от этот пост в блоге, в котором содержится больше объяснений по поводу этой техники. И чтобы увидеть все в контексте, вот весь исходный код для класса, о котором говорит автор в своем блоге пост.