24832


Windows10、VisualStudio2015、.Net4.6、WPF、C# で開発しております。

以下のようにIsMouseOverを利用して自動的に隠れるコントロールを作っているのですが、コンテキストメニューを開くとIsMouseOverがFalseになってしまうようで期待する動作になりません。

コンテキストメニューが開かれているときもコントロールを表示状態にしたいのですが、どのようにしたら良いのでしょうか。

* * *

MainWindow.xaml

[code]

    <Window x:Class="ContextMenu.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ContextMenu"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
    
        <Grid VerticalAlignment="Bottom" Background="LightGray">
    
            <!-- MouseOver状態で自動的に表示する -->
            <Grid.Style>
                <Style TargetType="Grid">
                    <Setter Property="Opacity" Value="0.1"/>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Opacity" Value="1"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Grid.Style>
    
            <TextBox Text="ここでコンテキストメニューを開くと..." HorizontalAlignment="Left" Margin="10"/>
    
        </Grid>
    
    </Grid>
    </Window>
    
[/code]


CC BY-SA 3.0
2016-05-13T05:57:20.947
24816
2016-05-14T06:25:31.840
14817
question
wpf
WPF 自動的に隠れるコントロールを作る時のコンテキストメニューの問題


`ContextMenu`を自分で指定している場合は`ContextMenu`が表示されている場合(`IsOpen="True"`)のトリガーを追加してやればいいのではないでしょうか。

[code]

    <Grid
        VerticalAlignment="Bottom"
        Background="LightGray">
    
        <!-- MouseOver状態で自動的に表示する -->
        <Grid.Style>
            <Style
                TargetType="Grid">
                <Setter
                    Property="Opacity"
                    Value="0.1" />
                <Style.Triggers>
                    <Trigger
                        Property="IsMouseOver"
                        Value="True">
                        <Setter
                            Property="Opacity"
                            Value="1" />
                    </Trigger>
                    <!-- トリガーを追加 -->
                    <DataTrigger
                        Binding="{Binding ElementName=tb, Path=ContextMenu.IsOpen}"
                        Value="True">
                        <Setter
                            Property="Opacity"
                            Value="1" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Grid.Style>
    
        <!-- Nameを設定 -->
        <TextBox
            Name="tb"
            Text="ここでコンテキストメニューを開くと..."
            HorizontalAlignment="Left"
            Margin="10">
            <!-- ContextMenuを設定 -->
            <TextBox.ContextMenu>
                <ContextMenu>
                    <MenuItem
                        Header="a"></MenuItem>
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    
    </Grid>
    
[/code]

また`TextBoxBase`などに用意されているWPF標準ポップアップは通常のプロパティやイベントでは判断できないので、

  1. `PropertyDescriptor`を使って`IsMouseOver`イベントをハンドルする。
  2. `PresentationSource`から対象の`Popup`を探す。

という2個のトリックを使う必要があります。

[code]

    public MainWindow()
    {
        InitializeComponent();
    
        // IsMouseOverの変更イベントをハンドルする。
        var dpd = DependencyPropertyDescriptor.FromProperty(UIElement.IsMouseOverProperty, typeof(Grid));
        dpd.AddValueChanged(grid, Grid_IsMouseOverChanged);
    }
    
    private void Grid_IsMouseOverChanged(object sender, EventArgs e)
    {
        // IsMouseOverがTrueの場合は処理終了
        if (grid.IsMouseOver)
        {
            grid.Opacity = 1;
            return;
        }
    
        // マウスが実際に乗っているか判定する。
        var mp = Mouse.GetPosition(grid);
    
        if (0 < mp.X && mp.X < grid.ActualWidth
            && 0 < mp.Y && mp.Y < grid.ActualHeight)
        {
            // 内部の要素に対して表示されているPopupを探す
            var popup = PresentationSource.CurrentSources
                        .OfType<HwndSource>()
                        .Select(_ => ((_.RootVisual as FrameworkElement)?.Parent as Popup))
                        .FirstOrDefault(_ => _?.IsOpen == true && _.PlacementTarget == tb);
    
            // TODO:PlacementTargetに対する条件を任意の要素に対応できるよう変える
    
            if (popup != null)
            {
                // PopupのClosedイベントを処理する
                popup.Closed += Popup_Closed;
                return;
            }
        }
        grid.Opacity = 0.1;
    }
    
    private void Popup_Closed(object sender, EventArgs e)
    {
        grid.Opacity = grid.IsMouseOver ? 1 : 0.1;
        ((Popup)sender).Closed -= Popup_Closed;
    }
    
[/code]


CC BY-SA 3.0
2016-05-13T07:33:18.520
24820
2016-05-13T11:38:25.200
2016-05-13T11:38:25.200
5750
5750
24816
answer


別のフォーカス対象が必要になりますが、`IsFocused`を使用した解決方法を思いつたので投稿します。

追記：自動的に隠れる条件に「フォーカスされていない」を加えることを許容する場合の回答になります

[code]

    <Grid>
        <!-- 別のフォーカス用 -->
        <Grid x:Name="other" Focusable="True" Background="#FEEE" MouseDown="other_MouseDown" FocusVisualStyle="{x:Null}"/>
    
        <!-- 自動的に隠したいパネル -->
        <Grid  x:Name="grid" VerticalAlignment="Bottom" >
    
            <!-- MouseOver状態で自動的に表示する -->
            <Grid.Style>
                <Style TargetType="Grid">
                    <Setter Property="Background" Value="LightGray"/>
                    <Setter Property="Opacity" Value="0.1"/>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Opacity" Value="1"/>
                        </Trigger>
                        <DataTrigger Binding="{Binding ElementName=tb, Path=IsFocused}" Value="True">
                            <Setter Property="Opacity" Value="1" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Grid.Style>
    
            <TextBox x:Name="tb" Text="ここでコンテキストメニューを開くと..." HorizontalAlignment="Left" Margin="10"/>
    
        </Grid>
    </Grid>
    
[/code]

* * *
[code]

        private void other_MouseDown(object sender, MouseButtonEventArgs e)
        {
            this.other.Focus();
        }
    
[/code]


CC BY-SA 3.0
2016-05-13T16:16:21.330
24832
2016-05-14T06:25:31.840
2016-05-14T06:25:31.840
14817
14817
24816
answer