スタイルセッターにブレンド動作を追加する方法


88

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」には、アクセシブルなセッターがありません。

回答:


76

私は同じ問題を抱えていて、解決策を考え出しました。私はそれを解決した後にこの質問を見つけました、そして私は私の解決策がマークのものと多くの共通点を持っているのを見る。ただし、このアプローチは少し異なります。

主な問題は、ビヘイビアとトリガーが特定のオブジェクトに関連付けられているため、複数の異なる関連付けられたオブジェクトにビヘイビアの同じインスタンスを使用できないことです。動作をインラインで定義すると、XAMLはこの1対1の関係を適用します。ただし、スタイルにビヘイビアーを設定しようとすると、そのスタイルが適用されるすべてのオブジェクトに再利用される可能性があり、これにより基本ビヘイビアークラスで例外がスローされます。実際、著者は、それが機能しないことを知って、私たちがこれを行おうとさえしないようにかなりの努力をしました。

最初の問題は、コンストラクターが内部であるため、動作セッター値を構築することさえできないことです。したがって、独自の動作とトリガーコレクションクラスが必要です。

次の問題は、ビヘイビアーとトリガーがアタッチされたプロパティにセッターがないため、インラインXAMLでのみ追加できることです。この問題は、主要な動作を操作し、プロパティをトリガーする独自のアタッチされたプロパティを使用して解決します。

3番目の問題は、動作コレクションが単一のスタイルターゲットにのみ適していることです。これは、あまり使用されていない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が最初はnullになる可能性があることに気付きました(使用するコントロールによって異なる場合があります。これは、Infragistics XamDataTreeのXamDataTreeNodeControlで使用しています)。そこで、OnPropertyTriggersChangedに少しサニティチェックを追加しました:if(e.NewValue!= null)
MetalMikester 2011

暗黙のスタイルでセッターを適用するときに、このアプローチに問題があった人はいますか?非暗黙的なスタイル(キーを持つスタイル)で正常に動作するようになりましたが、暗黙的なスタイルの場合は循環参照例外が発生します。
ジェイソンフランク

1
ニースのソリューションが、理由のx、残念ながらそれは、WinRTのでは機能しません:共有が...このプラットフォーム上に存在しない
トーマス・レベスク

1
このソリューションが機能することを確認できます。共有していただきありがとうございます。しかし、私はまだ暗黙のスタイルでそれを試していません。
魔王ゴルベリウス2013

2
@ジェイソンフランク、ありがとう、他の人への参照と同じように...私はそれを暗黙と明示の両方の場合に機能させるようにしました。実際、私は他の人を助けるためにすべてのコードをどこに置くかという質問をしますが、誰かが私の質問が重複していると推定します。私が見つけたすべてを与える私自身の質問に答えることはできません。私はかなりいいものを発見したと思います。:-( ...私はそれがあまりにも頻繁にその振る舞いは有用な情報から、他のユーザーを奪うため、発生しません願っています。
エリックOuellet

27

答えとこの素晴らしい記事BlendBehaviors 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を作成し、スタイル内の各コンポーネントの動作のインスタンスを登録しました。詳細については、リンクを参照してください。


チャームのように機能します!Scrollingbehaviorを組み合わせて、InnerRowDetailsTemplateを削除しました-Datagridsが親Datagridsをスクロールしません。
フィリップミハルスキー2015

喜んでお手伝いします、お楽しみください=)
ローマボロドフ2015

1
ビヘイビアの依存関係プロパティを使用したデータバインディングはどうですか?
JobaDiniz 2016

ユーザーに連絡する方法や、個人的にネガティブフィードバックで編集を拒否する方法がわかりません。@Der_Meisterや他の編集者の皆様、編集する前にコードを注意深くお読みください。他のユーザーや私の評判にも影響を与える可能性があります。この場合、IsEnabledForStyleプロパティを削除し、それを静的メソッドに一貫して置き換えることで、この質問の要点であるxamlでプロパティにバインドする可能性を破壊します。つまり、最後までコードを読んでいないようです。残念ながら、大きなマイナスで編集を拒否することはできませんので、今後はご注意ください。
Roma Borodov 2018

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が認識されないか、アクセスできないと表示されますか?
Igor Meszaros 2017

申し訳ありませんが、コメントするとすぐに、GetIsSingleClickEditModeは、DependencyProperty.RegisterAttachedに渡した文字列と一致する必要があることに気付きました
Igor Meszaros 2017

OnDetachingは別のイベントハンドラーを追加します。これは修正する必要があります(投稿を編集するときに1文字を変更することはできません...)
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>

ただし、動作ごとにこれを作成する必要があるのは、ちょっとしたPITAです。
スティーブンドリュー

0

動作コードはビジュアルを想定しているため、ビジュアルにのみ追加できます。したがって、私が見ることができる唯一のオプションは、ControlTemplate内の要素の1つに追加して、動作をスタイルに追加し、特定のコントロールのすべてのインスタンスに影響を与えることです。


0

記事WPFの添付動作の概要」は、スタイルのみを使用して添付動作を実装しており、関連している場合や役立つ場合もあります。

「添付された動作の概要」の記事の手法では、スタイルで使用して、インタラクティブタグを完全に回避します。これがより時代遅れのテクニックであるという理由だけであるのか、それともいくつかのシナリオでそれを好むべきであるいくつかの利点をまだ与えるのかどうかはわかりません。


2
これはBlendの動作ではなく、単純なアタッチされたプロパティを介した「動作」です。
スティーブンドリュー

0

このスレッドのRomanDvoskinとJonathanAllenの回答が示すアプローチが好きです。しかし、私が最初にそのテクニックを学んだとき、私はテクニックについてのより多くの説明を提供するこのブログ投稿から恩恵を受けました。そして、すべてをコンテキストで確認するために、作成者がブログ投稿で説明しているクラスのソースコード全体を以下に示します


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にどのように答えますか?トリガーは、回答のスタイルを介して追加されません。
クリプトス2017

0

この回答に基づいて必要なクラスは1つだけで、動作に他の何かを実装する必要がない、より単純なソリューションを作成しました。

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>

ArrayListを使用するには、次のxmlnsを追加することを忘れないでください。

xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.