2

I would like to have a popup show at the bottom of each textbox in my window, as they are focused. The user would be presented with the last few entries entered in that textbox. I would like the placement to be such that it would be at the bottom of the textbox currently focused.

This is my user control with the textbox:

<UserControl x:Class="PopupPlacement.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <TextBox Name="TextBox_MyControl" Text="enter your text here" Height="25" Width="200"/>
    </StackPanel>
</UserControl>

Here is my window:

<Window x:Class="PopupPlacement.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:PopupPlacement"
        Title="MainWindow" Height="450" Width="800">
    <Canvas>
        <Grid ShowGridLines="False">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Label Content="Domain" Margin="10"/>
            <local:MyControl Grid.Column="1" x:Name="Domain" Margin="10"/>
            <Label Grid.Row="1" Content="Username" Margin="10"/>
            <local:MyControl Grid.Row="1" Grid.Column="1" x:Name="Username" Margin="10"/>
            <Label Grid.Row="2" Content="Password" Margin="10"/>
            <local:MyControl Grid.Row="2" Grid.Column="1" x:Name="Password" Margin="10"/>
            <Button Grid.Row="3" Content="OK" Margin="10" Name="Button_OK"/>                                        
            <Button Grid.Row="3" Grid.Column="1" Content="Cancel" Margin="10"/>
            <Popup PlacementTarget="{Binding ElementName=TextBox_MyControl}" Placement="Bottom"
                   IsOpen="{Binding ElementName=TextBox_MyControl, Path=IsKeyboardFocused}">
                <ComboBox IsDropDownOpen="True">
                    <ComboBoxItem IsSelected="True">Item 1</ComboBoxItem>
                    <ComboBoxItem>Item 2</ComboBoxItem>
                </ComboBox>
            </Popup>
        </Grid>

    </Canvas>
</Window>

Appreciate any pointers.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
opedroso
  • 103
  • 7

1 Answers1

0

For me, the best solution to a similar requirement was to write a Behavior that kind of mimics Intellisense.

I don't have any simple code at hand, but you could create and show a ListBox inside a Popup placed at the AssociatedObject's bottom. You can then bind the TextBox-related entries to the Behavior via a DependencyProperty.

Of course, there's a lot more to it like closing the Popup, re-using existing controls, handling key presses to access the ListBox, insert the selected value to the TextBox etc.

Here's a simple (untested) sketch.

public class IntellisenseBehavior : Behavior<TextBox>
{
        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(IntellisenseBehavior), new UIPropertyMetadata(null));

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.GotKeyboardFocus += AssociatedObjectOnGotKeyboardFocus;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.GotKeyboardFocus -= AssociatedObjectOnGotKeyboardFocus;

            //cleanup
        }

        private void AssociatedObjectOnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            var popup = new Popup
            {
                ClipToBounds = false,
                AllowsTransparency = true,
                PopupAnimation = PopupAnimation.Fade,
                HorizontalAlignment = HorizontalAlignment.Left
            };

            popup.SetValue(FocusManager.IsFocusScopeProperty, true);
            popup.Placement = PlacementMode.Bottom;
            popup.PlacementTarget = AssociatedObject;

            var shadow = new SystemDropShadowChrome { Color = Colors.Transparent, MaxHeight = 200, Margin = new Thickness(0, 0, 5, 5) };
            var listBox = new ListBox
            {
                ItemsSource = ItemsSource
            }

            ((IAddChild)shadow).AddChild(listBox);
            ((IAddChild)popup).AddChild(shadow);

            popup.IsOpen = true;
        }
}

Attach it to all TextBoxes that you require to have this functionality and for instance use a converter to get the filtered entries you need.

<!-- Uses converter's public const string NameBox = "NameBox"; for filtering. -->
<TextBox>
   <i:Interaction.Behaviors>
      <IntellisenseBehavior ItemsSource="{Binding LastEntries, Converter={StaticResource FilterEntriesConverter}, ConverterParameter={x:Static FilterEntriesConverter.NameBox}}" />
   </i:Interaction.Behaviors>
</TextBox>

Hope that helps.

nirr
  • 61
  • 1
  • 2