Можете ли вы просто обернуть его одним способом? В соответствии с документами
: if - указывает метод, proc или строку для вызова, чтобы определить, должна ли выполняться проверка (например, если:: allow_validation или если: Proc.new {| user | user.signup_step> 2}). Метод, proc или string должен возвращать или оценивать истинное или ложное значение.
blockquote>validates :description, presence: true, if: :some_validation_check def some_validation_check first_step? || require_validation end
У меня есть другая идея, чтобы избежать создания прикрепленного свойства для каждого поведения:
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
}
public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>, IBehaviorCreator
{
//some code ...
public Behavior Create()
{
// here of course you can also set properties if required
return new SingleClickEditDataGridCellBehavior();
}
}
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="helper:BehaviorInStyleAttacher.Behaviors" >
<Setter.Value>
<helper:BehaviorCreatorCollection>
<behaviors:SingleClickEditDataGridCellBehavior/>
</helper:BehaviorCreatorCollection>
</Setter.Value>
</Setter>
</Style>
Объявить индивидуальное поведение / триггер как ресурсы:
<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>
В статье Введение в Attached Behaviors в WPF реализуется связанное поведение с использованием только стиля, а также может быть связано или полезно.
Техника в «Введении к приложенным поведением» "статья избегает тегов интерактивности, используя стиль. Я не знаю, является ли это только потому, что это более датированная методика, или, если это еще приносит некоторые преимущества, когда в некоторых сценариях это следует предпочесть.
Я не смог найти оригинальную статью, но мне удалось восстановить эффект.
#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>
Мне нравится подход, показанный в ответах Романа Двоскина и Джонатана Аллена в этой теме. Когда я впервые изучил эту технику, я воспользовался этой записью в блоге , которая дает больше объяснений по поводу этой техники. И чтобы увидеть все в контексте, здесь весь исходный код для класса, о котором говорит автор в своем сообщении в блоге.
Код поведения ожидает Visual, поэтому мы можем добавить его только на визуальный. Поэтому единственный вариант, который я мог видеть, - это добавить к одному из элементов внутри ControlTemplate, чтобы получить поведение, добавленное к стилю, и повлиять на все экземпляры определенного элемента управления.
У меня была такая же проблема, и я придумал решение. Я нашел этот вопрос после того, как решил его, и вижу, что мое решение имеет много общего с Марком. Однако этот подход немного отличается.
Основная проблема заключается в том, что поведение и триггеры ассоциируются с определенным объектом, поэтому вы не можете использовать один и тот же экземпляр поведения для нескольких разных связанных объектов. Когда вы определяете свое поведение, встроенный 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);
}
}
и там у вас есть , полнофункциональное поведение и триггеры, применяемые через стили.
Суммируя ответы и эту замечательную статью Blend Behaviors in Styles , я пришел к этому родовому короткому и удобному решению:
Я сделал общий класс, который может быть унаследован любым поведением .
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, зарегистрировав экземпляр поведения для каждого компонента в стиле. Для получения дополнительной информации см. Ссылку.
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>