code

스타일 설정 기에서 블렌드 비헤이비어를 추가하는 방법

codestyles 2020. 9. 25. 07:54
반응형

스타일 설정 기에서 블렌드 비헤이비어를 추가하는 방법


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>

모든 동작에 대해 연결된 속성의 생성을 방지하기위한 또 다른 아이디어가 있습니다.

  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>
    

원작은 찾을 수 없지만 효과를 재현 할 수있었습니다.

#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 내부의 요소 중 하나에 추가하는 것입니다.


The article Introduction to Attached Behaviors in WPF implements an attached behavior using Style only, and may also be related or helpful.

The technique in the "Introduction to Attached Behaviors" article avoids the Interactivity tags altogether, using on Style. I don't know if this is just because it is a more dated technique, or, if that still confers some benefits where one should prefer it in some scenarios.


I like the approach shown by the answers by Roman Dvoskin and Jonathan Allen in this thread. When I was first learning that technique though, I benefited from this blog post which provides more explanation about the technique. And to see everything in context, here is the entire source code for the class that the author talks about in his blog post.


Declare individual behavior/trigger as Resources :

<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>

Insert them in the collection :

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

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

</Button>

참고URL : https://stackoverflow.com/questions/1647815/how-to-add-a-blend-behavior-in-a-style-setter

반응형