wpf Popup.IsOpen binding in code-behind doesnt wor

2019-08-16 11:10发布

问题:

I am new to WPF and XAML and cant find what I am doing wrong here. My UserControl has few controls, and I need to control Popup.IsOpen from code-behind.

public bool PopupVisible
{
    get 
    {
        //true
        if( txtSearch.IsKeyboardFocusWithin ) return true;               
        if( txtSearch.IsMouseOver ) return true;

        //false                
        if( txtSearch.IsKeyboardFocusWithin == false &&
            ( grid.IsMouseOver == false || popContainer.IsMouseOver == false || popContainer.IsMouseOver == false)
            )
        {
            return false;
        }

        return false;      
    }
}

My XAML:

    <UserControl x:Class="Controls.KlasifikacijaBolestSearchBox.KlasifikacijaBolestSearchBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:controls="clr-namespace:Controls"
             xmlns:itemClass="clr-namespace:Controls.KlasifikacijaBolestSearchBox.ViewModels"
             x:Name="this"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" Loaded="this_Loaded">
    <Grid x:Name="grid" DataContext="{Binding Class}">
        <DockPanel LastChildFill="False">
            <controls:SearchTextBox x:Name="txtSearch" DockPanel.Dock="Top" TextChanged="txtSearch_TextChanged" OnSearch="txtSearch_OnSearch" Background="WhiteSmoke"></controls:SearchTextBox>
        </DockPanel>
        <Popup x:Name="popContainer"
                   Placement="Bottom"
                   PlacementTarget="{Binding ElementName=txtSearch}"               
                   AllowsTransparency="True"
                   StaysOpen="True"
                   IsOpen="{Binding PopupVisible, Mode=TwoWay}"
                   PopupAnimation="Fade"
                   MinWidth="500"
                   MinHeight="500"
                   Width="{Binding ElementName=txtSearch, Path=ActualWidth}">
            <DockPanel LastChildFill="True">
                <TextBlock DockPanel.Dock="Top" TextAlignment="Center" Text="Klasifikacija bolesti" FontWeight="Bold" Background="{DynamicResource WindowBackgroundBrush}" Foreground="White">
                </TextBlock>
                <TreeView
                            DockPanel.Dock="Top"
                            x:Name="tvKlasifikacijaBolest" 
                            ItemTemplate="{StaticResource tvKlasifikacijaBolestHDStyle}" 
                            ItemContainerStyle="{StaticResource tvKlasifikacijaBolestItemStyle}"
                            ScrollViewer.CanContentScroll="True"
                            ScrollViewer.VerticalScrollBarVisibility="Visible"
                            MouseDoubleClick="tvKlasifikacijaBolest_MouseDoubleClick">
                </TreeView>
            </DockPanel>
        </Popup>
    </Grid>
</UserControl> 

Then I was thinking I need to implement INotifyPropertyChanged so I change my code to this:

private bool popupOpen;
public bool PopupOpen
{
    get { return popupOpen; }
    set
    {
        popupOpen = value;
        OnNotifyPropertyChanged("PopupOpen");
    }
}

private void txtSearch_TextChanged(string searchText, string searchType)
{
    PopupOpen = true;
}

And of course changed my XAML part:

<Popup x:Name="popContainer"
       IsOpen="{Binding PopupOpen, Mode=TwoWay}">
    <!-- ...  -->
</Popup>

And my popup never gets Opened. At first I used datatriggers and it work fine until I turn AllowTransparency="True" then my triggers had some issue when I move mouse over my control some part of poup is transparent and it closes popup so I changed to code-behind but I dont know what I am doing wrong.

Bottom line is - I need my popup to be open while txtSearch.IsMouseOver or IsKeyboardFocusedWithin, or my treeview IsMouseOver or any child componenet in popup control IsMouseOver = true (border, label, textblock...). If txtSearch has focus then popup need to stay open no metter if IsMouseOver on Popup elements is True or False.

回答1:

You have two problems.

Note: Two paragraphs below is only for educational purposes.

The first. The PopupVisible never notifies that it has changed. To notify targets you need to change this to make it a read-only DependencyPrperty:

private static readonly DependencyPropertyKey IsPopupOpenPropertyKey = DependencyProperty.RegisterReadOnly(
    "IsPopupOpen",
    typeof(bool),
    typeof(MainWindow),
    new FrameworkPropertyMetadata(false)
    );
private static readonly DependencyProperty IsPopupOpenProperty = IsPopupOpenPropertyKey.DependencyProperty;

public bool IsPopupOpen
{
    get { return (bool)GetValue(IsPopupOpenProperty); }
    private set { SetValue(IsPopupOpenPropertyKey, value); }
}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
    base.OnPropertyChanged(e);

    if (e.Property == UIElement.IsMouseOverProperty ||
        e.Property == UIElement.IsKeyboardFocusWithinProperty)
    {
        IsPopupOpen =
            txtSearch.IsMouseOver ||
            txtSearch.IsKeyboardFocusWithin;
    }
}

The second. When you bind to the property of a control then you must specify ElementName or RelativeSource. Otherwise, a Binding will see for the property in the inherited data context.

<UserControl x:Class="Controls.KlasifikacijaBolestSearchBox.KlasifikacijaBolestSearchBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:searchBox="Controls.KlasifikacijaBolestSearchBox">
    <!-- ... -->
    <Popup x:Name="popContainer"
           IsOpen="{Binding IsPopupOpen,
                            RelativeSource={RelativeSource AncestorType={x:Type searchBox:KlasifikacijaBolestSearchBox}},
                            Mode=OneWay}">

Solution. To achieve the listed behavior you should use a IMultiValueConverter and a MultiBinding.

public sealed class OrConverter : IMultiValueConverter
{
    public object Convert(object[] values, System.Type targetType, object parameter, CultureInfo culture)
    {
        foreach (bool value in values)
        {
            if (value) { return true; }
        }

        return false;
    }

    public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

XAML:

<UserControl x:Class="Controls.KlasifikacijaBolestSearchBox.KlasifikacijaBolestSearchBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:controls="clr-namespace:Controls"
         xmlns:converters="clr-namespace:Converters"
         xmlns:itemClass="clr-namespace:Controls.KlasifikacijaBolestSearchBox.ViewModels"
         x:Name="this"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Loaded="this_Loaded">
<UserControl.Resources>
    <converters:OrConverter x:Key="Converter" />
</UserControl.Resources>
<Grid x:Name="grid" DataContext="{Binding Class}">
    <DockPanel LastChildFill="False">
        <TextBox x:Name="txtSearch"
                 Background="WhiteSmoke"
                 DockPanel.Dock="Top" />
    </DockPanel>
    <Popup x:Name="popContainer"
           Width="{Binding ElementName=txtSearch,
                           Path=ActualWidth}"
           MinWidth="500"
           MinHeight="500"
           AllowsTransparency="True"
           Placement="Bottom"
           PlacementTarget="{Binding ElementName=txtSearch}"
           PopupAnimation="Fade"
           StaysOpen="True">
        <Popup.IsOpen>
            <MultiBinding Converter="{StaticResource OrConverter}">
                <Binding ElementName="txtSearch" Path="IsMouseOver" Mode="OneWay" />
                <Binding ElementName="txtSearch" Path="IsKeyboardFocusWithin" Mode="OneWay" />
                <Binding ElementName="popContainer" Path="IsMouseOver" Mode="OneWay" />
            </MultiBinding>
        </Popup.IsOpen>


回答2:

Try to handle Opened and Closed events of Popup. Here's an example: Popup.Closed Event