Button에 대한 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>
오류가 발생합니다
“Behaviors”속성에 액세스 할 수있는 setter가 없습니다.
답변
나는 같은 문제가 있었고 해결책을 찾았습니다. 이 질문을 해결 한 후이 질문을 찾았고 내 솔루션이 Mark와 많은 공통점이 있음을 알았습니다. 그러나이 접근 방식은 약간 다릅니다.
주요 문제는 동작과 트리거가 특정 개체와 연결되어 있으므로 여러 다른 연결된 개체에 대해 동일한 동작 인스턴스를 사용할 수 없다는 것입니다. 동작을 정의 할 때 인라인 XAML은이 일대일 관계를 적용합니다. 그러나 스타일에서 비헤이비어를 설정하려고하면 스타일이 적용되는 모든 객체에 대해 재사용 될 수 있으며 기본 비헤이비어 클래스에서 예외가 발생합니다. 사실 저자들은 우리가이 작업을 시도하는 것을 막기 위해 상당한 노력을 기울였습니다.
첫 번째 문제는 생성자가 내부이기 때문에 동작 setter 값을 구성 할 수도 없다는 것입니다. 그래서 우리는 우리 자신의 행동과 트리거 컬렉션 클래스가 필요합니다.
다음 문제는 동작 및 트리거 연결된 속성에 setter가 없으므로 인라인 XAML로만 추가 할 수 있다는 것입니다. 이 문제는 기본 동작을 조작하고 속성을 트리거하는 자체 연결된 속성으로 해결합니다.
세 번째 문제는 비헤이비어 컬렉션이 단일 스타일 대상에만 적합하다는 것입니다. 이 문제 x:Shared="False"
는 참조 할 때마다 리소스의 새 복사본을 만드는 거의 사용되지 않는 XAML 기능 을 활용하여 해결합니다 .
마지막 문제는 동작과 트리거가 다른 스타일 설정자와 같지 않다는 것입니다. 우리는 이전 행동을 새로운 행동으로 바꾸고 싶지 않습니다. 왜냐하면 그들은 매우 다른 일을 할 수 있기 때문입니다. 따라서 일단 동작을 추가 한 후에는 제거 할 수 없다는 것을 받아 들인다면 (현재 동작이 작동하는 방식입니다), 동작과 트리거가 추가되어야하며 연결된 속성에 의해 처리 될 수 있다는 결론을 내릴 수 있습니다.
다음은이 접근 방식을 사용하는 샘플입니다.
<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 생성 및 연결된 속성 설정
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/>
</Style>
답변
모든 동작에 대해 연결된 속성을 생성하지 않으려는 또 다른 아이디어가 있습니다.
-
행동 생성자 인터페이스 :
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>
답변
원작은 찾을 수 없지만 효과를 재현 할 수있었습니다.
#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 내부의 요소 중 하나에 추가하는 것입니다.
답변
WPF의 연결된 동작 소개 문서 는 스타일 만 사용하여 연결된 동작을 구현하며 관련되거나 도움이 될 수도 있습니다.
“Introduction to Attached Behaviors”기사의 기술은 on Style을 사용하여 상호 작용 태그를 모두 피합니다. 이것이 더 오래된 기술이기 때문인지 또는 일부 시나리오에서 선호해야하는 몇 가지 이점이 여전히 부여되는지 여부는 알 수 없습니다.