10

WPFを開く状況を想像してみてくださいPopup(たとえば、ButtonClickを使用)。にListBox直接Popupいくつかのアイテムがあるので、スクロールできる必要があります。Custom Controlこれがあなたのものであり、にあると想像してくださいScrollViewer

さて、マウスを表面から外側に動かしてPopupスクロールすると、どうなりますか?上下にスクロールしますが、Popup開いたままです!そしてそれが問題です。

問題は、コントロール内から、VisualTree内の他の不明な親コントロールがスクロールし始めたことをどのように検出するかです。連続して設定しIsDropDownOpen = falseますか?

4

3 に答える 3

11

内に含まれる要素で使用するトリガーを作成できますScrollViewer。完全なサンプル アプリケーションは次のとおりです。

<Grid>
    <ScrollViewer VerticalAlignment="Top" Height="200">
        <StackPanel HorizontalAlignment="Left">
            <Button Name="button" Content="Open">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=popup}" PropertyName="IsOpen" Value="True"/>
                    </i:EventTrigger>
                    <local:ScrollTrigger>
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=popup}" PropertyName="IsOpen" Value="False"/>
                    </local:ScrollTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Popup Name="popup" PlacementTarget="{Binding ElementName=button}">
                <TextBlock Background="White" Text="Sample text"/>
            </Popup>
            <Rectangle Width="100" Height="100" Fill="Red"/>
            <Rectangle Width="100" Height="100" Fill="Green"/>
            <Rectangle Width="100" Height="100" Fill="Blue"/>
            <Rectangle Width="100" Height="100" Fill="Yellow"/>
        </StackPanel>
    </ScrollViewer>
</Grid>

を開くボタンがありPopup、任意の親でスクロールするとScrollViewerアクションScrollTriggerが発生し、ポップアップを閉じることができます。トリガーはButtonではなく に接続されていることに注意してくださいPopup。ビジュアル ツリーにある近くの要素を使用できます。また、別のトリガーを使用して開くことに注意してください。ただしPopup、どのように開くかは元の質問にとって重要ではありません。

は次のScrollTriggerとおりです。

class ScrollTrigger : TriggerBase<FrameworkElement>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
    }

    void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (var scrollViewer in GetScrollViewers())
            scrollViewer.ScrollChanged += new ScrollChangedEventHandler(scrollViewer_ScrollChanged);
    }

    void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        InvokeActions(e.OriginalSource);
    }

    IEnumerable<ScrollViewer> GetScrollViewers()
    {
        for (DependencyObject element = AssociatedObject; element != null; element = VisualTreeHelper.GetParent(element))
            if (element is ScrollViewer) yield return element as ScrollViewer;
    }
}

ScrollTriggerは非常に単純です。すべての親イベントにアタッチし、ScrollChanged含まれているアクションを起動するだけです。サンプルでは、​​ を使用してChangePropertyActionを閉じますPopup

動作に慣れていない場合は、Expression Blend 4 SDK をインストールして、次の名前空間を追加してください。

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

System.Windows.Interactivityプロジェクトにとを追加Microsoft.Expression.Interactionsします。

于 2011-01-25T03:36:02.903 に答える
1

コントロールがどのようになっているのかよくわかりませんが、Focus イベントに基づいてコントロールの開閉を行うことはできませんか? フォーカスを失った場合、ポップアップを閉じますか? 理解が間違っているかもしれませんが、コード スニペットを投稿できますか? ダニエル

于 2010-02-23T15:43:26.367 に答える
1

警告: これは長いコメントです。基本的には、@ Rick Sladkeyの応答に対する私の変更を説明しているだけです。それは素晴らしい出発点でしたが、私が見たいくつかのことでいくつかの変更が加えられたことに気付きました.

カスタムコントロールを実行しているときに、これに似たものが必要でした(スクロールのポップアップを閉じたいと思っていました)、答えはRick Sladkeyの答えと非常に似ていることがわかりました。いくつかの小さな変更を加えて、いくつかのアイテムを改善するのに役立ちます.

私が行った変更は、主に 3 つの項目に関するものでした。1つ目は、ScrollViewer_ScrollChangedアクティブにスクロールしていないときにイベントが発生していることを確認していたことです(他のことが明らかにそれを引き起こしました)。次に、コントロールをアンロードするときにsScrollViewer_ScrollChangedから切り離されていなかったScrollViewerので、3 を追加してから 1 を削除してスクロールすると、2 ではなく 3 回起動します。コントロールの消費者が IsOpen プロパティを動的に設定できるようにする機能。

これで、変更したバージョンのScrollTriggerクラスは次のようになります。

public class ScrollTrigger : TriggerBase<FrameworkElement>
{
    public bool TriggerOnNoChange
    {
        get
        {
            var val = GetValue(TriggerOnNoChangeProperty);
            if (val is bool b)
            {
                return b;
            }

            return false;
        }
        set => SetValue(TriggerOnNoChangeProperty, value);
    }

    public static readonly DependencyProperty TriggerOnNoChangeProperty =
        DependencyProperty.Register(
            "TriggerOnNoChange", 
            typeof(bool), 
            typeof(ScrollTrigger), 
            new FrameworkPropertyMetadata(
                false, 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    protected override void OnAttached()
    {
        AssociatedObject.Loaded += AssociatedObject_Loaded;
        AssociatedObject.Unloaded += AssociatedObject_Unloaded;
    }

    private void AssociatedObject_Loaded(
        object sender, 
        RoutedEventArgs e)
    {
        foreach (var scrollViewer in GetScrollViewers())
            scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
    }

    private void AssociatedObject_Unloaded(
        object sender, 
        RoutedEventArgs e)
    {
        foreach (var scrollViewer in GetScrollViewers())
            scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
    }

    private void ScrollViewer_ScrollChanged(
        object sender,
        ScrollChangedEventArgs e)
    {
        if(TriggerOnNoChange ||
           Math.Abs(e.VerticalChange) > 0 || 
           Math.Abs(e.HorizontalChange) > 0)
            InvokeActions(e.OriginalSource);
    }

    private IEnumerable<ScrollViewer> GetScrollViewers()
    {
        for (DependencyObject element = AssociatedObject; 
             element != null; 
             element = VisualTreeHelper.GetParent(element))
            if (element is ScrollViewer viewer) yield return viewer;
    }
}

ここでの最初の変更はScrollViewer_ScrollChanged、オフセット値が実際に変更されたかどうかを確認するロジックを追加したことです。必要に応じてそのロジックをバイパスできるように、トリガーに依存関係プロパティを追加しました。関連付けられたオブジェクトにアンロード イベントを追加した 2 番目の変更。これにより、コントロールが削除された場合に、関連するアクションが に削除され、コントロールを動的に追加および削除するときに呼び出しが行われる ScrollViewers回数が減ります。ScrollViewer_ScrollChanged

これらの変更と、コントロールのコンシューマーがポップアップの表示方法を指定できるようにしたいという事実を考慮して、.xaml は次のようになりました。

<UserControl ...
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
             xmlns:tgrs="clr-namespace:NameSpace.To.ScrollTrigger.class.Namespace"
             x:Name="MyControlNameRoot"
             .../>
    <i:Interaction.Triggers>
        <tgrs:ScrollTrigger TriggerOnNoChange="False">
            <i:InvokeCommandAction Command="{Binding ElementName=MyCommandNameRoot, Path=ClosePopupCommand}"/>
        </tgrs:ScrollTrigger>
    </i:Interaction.Triggers>
    ...
    <Popup ...
           IsOpen="{Binding ElementName=MyControlNameRoot, Path=IsPopupOpen, Mode=OneWay}"
           .../>
        ...
    </Popup>
    ...
</UserControl>

バインドするものが必要になりました。カスタム コントロールを作成しているため、コード ビハインドにいくつかの依存関係プロパティとその他の項目を作成しました。MVVM でこのアプローチを使用している場合は、 ' INotifyProperty' を記述し、バインディングがそれらであることを確認する必要があります (方法によっては、バインディングの ElementName 部分が必要ない場合があります)。これを行うには多くの方法があります。わからない場合は、「mvvm データ バインディング INotifyPropertyChanged」をグーグルで検索すると、簡単に見つけることができます。

ちなみに、私も Prism を使っているのでDelegateCommands を使っていますが、好きな実装を使って構いICommandません。これで、コード ビハインドは次のようになりました。

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
         ClosePopupCommand = new DelegateCommand(OnPopupCommand);

        InitializeComponent();
    }
    ...
    public ICommand ClosePopupCommand { get; }
    private OnClosePopupCommand ()
    {
        IsPopupOpen = false;
    }

    public static readonly DependencyProperty IsPopupOpenProperty =
        DependencyProperty.Register(
            "IsPopupOpen", 
            typeof(bool), 
            typeof(MyUserControl), 
            new FrameworkPropertyMetadata(
                false,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public bool IsPopupOpen
    {
        get
        {
            var val = GetValue(IsPopupOpenProperty);
            if (val is bool b)
            {
                return b;
            }

            return false;
        }
        set => SetValue(IsPopupOpenProperty, value);
    }


    ...
}

これにより、実際に変更があり、不要な呼び出しがなく、開いているかどうかをユーザーが変更できるようにするスクロールトリガーで閉じるポップアップを作成できます。

ここまでできたら、よろしくお願いします。ご尽力に感謝いたします。これが少しでもお役に立てば幸いです。

于 2018-11-09T17:09:08.973 に答える