1

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.

Yoh Deadfall
  • 2,711
  • 7
  • 28
  • 32
Matija Gluhak
  • 260
  • 2
  • 13

2 Answers2

3

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>
Yoh Deadfall
  • 2,711
  • 7
  • 28
  • 32
  • i will try this when i get to comp, but can you explain why do i need IsPopupOpenPropertyKey, rest of code you wrote i understand more or less but what is purpose of that DPKey and will it work without it ? .. and i did put OnNotifyPropertyChanged("PopupOpen") on few places in code but didnt past that part of it. As part of second thing it is property of ingereted data contex .. it is property in code-behind cs – Matija Gluhak Apr 19 '15 at 14:05
  • The first two paragraphs are only for educational purposes. So you actually do not need `IsPopupOpenPropertyKey`. I demonstrated how read-only properties (like IsMouseOver, etc.) work and how they must be implemented to raise notifications because your `PopupVisible` has wrong implementation. The code with `PopupOpen` doesn't work because you didn't specify `RelativeSource` for the binding. It's described in the second section. – Yoh Deadfall Apr 19 '15 at 14:43
  • Somehow i make it work with my old code (didnt implement your code and i needed to remove "OneWay" mode and i had to put OnNotifyPropertyChanged("PopupOpen") on many events (mouse enter/leave on txtSearch, focus/lost focus on txtSearch, mouse enter/leave on popupContaint etc.) .. ITs messy code and i dont like it, and now you suggestion is nice and i did simular with Triggers and it work fine until you put "AllowTransparensy" = True on popup .. then when you mose move from searchbox to poup, popup just close, when i remove AllowTransparency it works fine. – Matija Gluhak Apr 19 '15 at 14:56
  • Try to use only the thrid paragraph. It's enough to keep popup opened. – Yoh Deadfall Apr 19 '15 at 15:12
  • It doesnt work ... if i set AllowTransparency=True ... when you move mouse from txtSearch to popup (in design and on winfow they are docked popup is just below txtSearch) .. but there is like 1 pixel or something between them and my popup got closed when i try to move mose over it besouse (IsMouseOver on txrSearch is False and IsMouseOver on popupContainer is still not True (that one tiny gap is between) .. dont know how to explain that :( – Matija Gluhak Apr 19 '15 at 15:20
  • true, i can set up timers but i was thinking to find where is that "MouseOver" while it pass from txtSearch to popup ... ifi put event on that one i dont need timer :) – Matija Gluhak Apr 19 '15 at 19:13
0

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

Sergey L.
  • 96
  • 4