Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
WPF Popup - Form Moved
Message
From
27/02/2008 13:22:47
 
 
General information
Forum:
ASP.NET
Category:
Coding, syntax and commands
Miscellaneous
Thread ID:
01295934
Message ID:
01296992
Views:
26
>>Everything is working good, except I need to close the popup if the user moves the window. The Popup does not stay with the textbox if the window is moved, and closing it is easier than making it follow.
>>
>>The question is... How do I drill down from my custom control to access an event that will tell me that the form has moved?
>
>Could you post the XAML for this?
>Best,
>Viv

Ok... It's a custom control, so it uses the Generic.xaml in the Themes folder.
<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:CDS3.ControlsWPF">
  <Style TargetType="{x:Type local:cdsPopUpListBox}">
    <Setter Property="Margin" Value="1"/>
    <Setter Property="Template">
      <!-- ScrollViewer must be set -->
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:cdsPopUpListBox}">
          <Border 
            Name="Border" 
            Background="WhiteSmoke"
            BorderBrush="Black" 
            BorderThickness="1">
            <ScrollViewer 
              Margin="0"
              Focusable="false" VerticalScrollBarVisibility="Auto">
              <StackPanel Margin="2" IsItemsHost="True" />
            </ScrollViewer>
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
  <Style TargetType="{x:Type local:cdsAutoComplete}">
    <Setter Property="Template">
    <!--Create a TextBox that looks "flat".
        The control template for a TextBox or RichTextBox must
        include an element tagged as the content host.  An element is 
        tagged as the content host element when it has the special name
        PART_ContentHost.  The content host element must be a ScrollViewer,
        or an element that derives from Decorator.  
        -->
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:cdsAutoComplete}">
        <Border Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Padding="2,4,2,2">
          <Decorator x:Name="PART_ContentHost"/>
        </Border>
       </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>
It took me forever to figure out that I had to have ScrollViewer in there I only managed to figure it out by trial and error. A number of the attributes in there should probably be set back to TemplateBinding.

Then there is the code behind for the popup.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CDS3.ControlsWPF
  {
  /// <summary>
  /// Popup List box.
  /// </summary>
  public partial class cdsPopUpListBox : System.Windows.Controls.ListBox
    {

    //http://blogs.msdn.com/wpfsdk/archive/2007/04/27/popup-your-control.aspx

    Popup _parentPopup;

    public cdsPopUpListBox() : base()
      {
      LostKeyboardFocus += new KeyboardFocusChangedEventHandler(cdsPopUpListBox_LostKeyboardFocus);
      }

    static cdsPopUpListBox()
      {
      //This OverrideMetadata call tells the system that this element 
      //wants to provide a style that is different than its base class.
      //This style is defined in themes\generic.xaml
      DefaultStyleKeyProperty.OverrideMetadata(typeof(cdsPopUpListBox), 
                new FrameworkPropertyMetadata(typeof(cdsPopUpListBox)));
      }

    void cdsPopUpListBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
      {
      if (e.NewFocus == null)
        IsOpen = false;
      else if (!e.NewFocus.ToString().StartsWith("System.Windows.Controls.ListBoxItem:"))
        IsOpen = false;
      }

    //Placement
    public static readonly DependencyProperty PlacementProperty =
                Popup.PlacementProperty.AddOwner(typeof(cdsPopUpListBox));

    public PlacementMode Placement
      {
      get { return (PlacementMode)GetValue(PlacementProperty); }
      set { SetValue(PlacementProperty, value); }
      }

    //PlacementTarget
    public static readonly DependencyProperty PlacementTargetProperty =
       Popup.PlacementTargetProperty.AddOwner(typeof(cdsPopUpListBox));

    public UIElement PlacementTarget
      {
      get { return (UIElement)GetValue(PlacementTargetProperty); }
      set { SetValue(PlacementTargetProperty, value); }
      }

    //PlacementRectangle
    public static readonly DependencyProperty PlacementRectangleProperty =
                Popup.PlacementRectangleProperty.AddOwner(typeof(cdsPopUpListBox));

    public Rect PlacementRectangle
      {
      get { return (Rect)GetValue(PlacementRectangleProperty); }
      set { SetValue(PlacementRectangleProperty, value); }
      }

    //HorizontalOffset
    public static readonly DependencyProperty HorizontalOffsetProperty =
        Popup.HorizontalOffsetProperty.AddOwner(typeof(cdsPopUpListBox));

    public double HorizontalOffset
      {
      get { return (double)GetValue(HorizontalOffsetProperty); }
      set { SetValue(HorizontalOffsetProperty, value); }
      }

    //VerticalOffset
    public static readonly DependencyProperty VerticalOffsetProperty =
            Popup.VerticalOffsetProperty.AddOwner(typeof(cdsPopUpListBox));

    public double VerticalOffset
      {
      get { return (double)GetValue(VerticalOffsetProperty); }
      set { SetValue(VerticalOffsetProperty, value); }
      }

    public static readonly DependencyProperty IsOpenProperty =
            Popup.IsOpenProperty.AddOwner(
                    typeof(cdsPopUpListBox),
                    new FrameworkPropertyMetadata(
                            false,
                            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                            new PropertyChangedCallback(OnIsOpenChanged)));

    public bool IsOpen
      {
      get { return (bool)GetValue(IsOpenProperty); }
      set { SetValue(IsOpenProperty, value); }
      }
    
    private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
      cdsPopUpListBox ctrl = (cdsPopUpListBox)d;
      if ((bool)e.NewValue)
        {
        if (ctrl._parentPopup == null)
          {
          ctrl.HookupParentPopup();
          }
        }
      }

    private void HookupParentPopup()
      {
      _parentPopup = new Popup();
      _parentPopup.AllowsTransparency = true;
      Popup.CreateRootPopup(_parentPopup, this);
      }
    }
  }
The source for most of that code came from the link next to the class definition. I just changed it from a textbox to a listbox.

Now the text box:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;

namespace CDS3.ControlsWPF
  {
  /// <summary>
  /// Auto complete TextBox
  /// 
  /// current project: xmlns:MyNamespace="clr-namespace:CDS3.ControlsWPF"
  /// different project: xmlns:MyNamespace="clr-namespace:CDS3.ControlsWPF;assembly=CDS3.ControlsWPF"
  /// project reference :
  ///
  ///     Right click on the target project in the Solution Explorer and
  ///     "Add Reference"->"Projects"->[Browse to and select this project]
  /// 
  /// use your control in the XAML file: <MyNamespace:cdsAutoComplete/>
  /// </summary>
  public class cdsAutoComplete : TextBox 
    {

    public  IEnumerable<cdsAutoCompleteItems> AutoCompleteList;
    public  bool Constrain2List { get; set; }
    private int _ID;
    public int ID {
      get { return _ID; }
    set
      {
      _ID = value; 
      string nm = (from items in AutoCompleteList
                   where items.ID == value 
                   select items.Name).First();
      this.Text = nm;
      }
    }

    private cdsPopUpListBox popup;

    #region Constructors

    public cdsAutoComplete() : base()
      {
      popup = new cdsPopUpListBox();
      popup.PlacementTarget = this;
      LostKeyboardFocus += new KeyboardFocusChangedEventHandler(cdsAutoComplete_LostKeyboardFocus);
      KeyUp += new KeyEventHandler(cdsAutoComplete_KeyUp);
      popup.KeyUp += new KeyEventHandler(popup_KeyUp);
      Constrain2List = false;
      }

    static cdsAutoComplete()
      {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(cdsAutoComplete), new FrameworkPropertyMetadata(typeof(cdsAutoComplete)));
      }

    #endregion

    #region Events

    void cdsAutoComplete_KeyUp(object sender, KeyEventArgs e)
      {
      //var xConverter = new KeyConverter();
      //char xChar = xConverter.ConvertToString(e.Key)[0];
      if (e.Key == System.Windows.Input.Key.Enter)
        TogglePopup(false);
      else if (e.Key == System.Windows.Input.Key.Down)
        {
        object item = popup.Items[0];
        popup.SelectedItem = item;
        ListBoxItem lbi = (ListBoxItem)popup.ItemContainerGenerator.ContainerFromItem(item);
        lbi.Focus();
        }
      else
        {
        string entered = ((TextBox)sender).Text;
        popup.Items.Clear();
        var selected = (from items in AutoCompleteList
                        where items.Name.ToUpper().StartsWith(entered.ToUpper())
                        select items).Take(5);
        if (selected.Count()>0)
          {
          int selectstart = this.SelectionStart;
          this.Text       = selected.First().Name;
          this.SelectionStart = selectstart;
          this.SelectionLength = this.Text.Length - selectstart;
          //System.Windows.Forms.MessageBox.Show(this.SelectionStart.ToString());
          this.Text = selected.First().Name;
          foreach (var sel in selected)
            {
            popup.Items.Add(sel.Name);
            }
          TogglePopup(true);
          }
        else
          TogglePopup(false);
        }
      }

    void cdsAutoComplete_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
      {
      if (e.NewFocus == null)
        popup.IsOpen = false;
      else if (!e.NewFocus.ToString().StartsWith("System.Windows.Controls.ListBoxItem:"))
        popup.IsOpen = false;
      }

    void popup_KeyUp(object sender, KeyEventArgs e)
      {
      if (e.Key == System.Windows.Input.Key.Enter)
        {
        this.Text = popup.SelectedItem.ToString();
        TogglePopup(false);
        }
      }

    #endregion

    void TogglePopup(bool showPopup)
      {
      popup.MinWidth = this.ActualWidth;
      popup.IsOpen = showPopup;
      }

    }

  public class cdsAutoCompleteItems
    {
    public int ID { get; set; }
    public string Name { get; set; }

    public cdsAutoCompleteItems(int id,string name)
      {
      ID = id;
      Name = name;
      }
    }

  }
Now that code is still unfinished, and is real rough. I have more events to handle and more keyboard handling still to write, features to add, etc etc. Parts of it have not even been tested yet.

I fill it using Linq. (But any IEnumerable would do)
      cdsPayablesDCDataContext dc = new cdsPayablesDCDataContext();
      AutoCompBox.AutoCompleteList = from acts in dc.cdsEntities
                                  where acts.RecordType == 'A'
                                  select new cdsAutoCompleteItems(acts.ID, acts.Name.Trim());
And to use it:
    <!-- in the host controls header  xmlns:local="clr-namespace:CDS3.ControlsWPF"  -->
    <Grid.Resources>
      <Style TargetType="local:cdsAutoComplete">
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Background" Value="White"/>
        <Setter Property="Width" Value="250"/>
      </Style>
    </Grid.Resources>
    <local:cdsAutoComplete 
      Height="23" 
      HorizontalAlignment="Left" 
      Margin="43,15,0,0" 
      x:Name="AutoComp"
      VerticalAlignment="Top" 
      Constrain2List ="True" 
      Width="140" 
      BorderBrush="#FFA5ACB2" />
As I mentioned above, it's still pretty rough, and missing a number of event handlers.

But it does work.
Previous
Next
Reply
Map
View

Click here to load this message in the networking platform