Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Personally I hate WPF's built in <code>Popup</code> control for exactly those reasons, and my workaround is to use a <a href="http://rachel53461.wordpress.com/2011/08/20/popup-panel-for-wpf/" rel="nofollow">Custom Popup UserControl</a></p> <p>Basically I'll put the Popup in a panel that allows it's children to overlap, such as a <code>Grid</code> or a <code>Canvas</code>, and position it on top of whatever content it's supposed to be on top of. </p> <p>It includes DependencyProperties to specify it's parent panel and if it's open or not, and is part of the normal VisualTree so it will move around with your Window and act the same way any regular UI element would.</p> <p>Typical usage would look like this:</p> <pre><code>&lt;Grid x:Name="ParentPanel"&gt; &lt;ItemsControl ... /&gt; &lt;local:PopupPanel Content="{Binding PopupContent}" local:PopupPanel.PopupParent="{Binding ElementName=ParentPanel}" local:PopupPanel.IsPopupVisible="{Binding IsPopupVisible}" /&gt; &lt;/Grid&gt; </code></pre> <p>The code for the UserControl can be found <a href="http://rachel53461.wordpress.com/2011/08/20/popup-panel-for-wpf/" rel="nofollow">on my blog</a> along with a <a href="http://www.mediafire.com/?ca4dwi5obdof6wh" rel="nofollow">downloadable example of its use</a>, but I'll also post a copy of it here. </p> <p>The XAML for the UserControl is:</p> <pre><code>&lt;UserControl x:Class="PopupPanelSample.PopupPanel" 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:local="clr-namespace:PopupPanelSample" FocusManager.IsFocusScope="True" &gt; &lt;UserControl.Template&gt; &lt;ControlTemplate TargetType="{x:Type local:PopupPanel}"&gt; &lt;ControlTemplate.Resources&gt; &lt;!-- Converter to get Popup Positioning --&gt; &lt;local:ValueDividedByParameterConverter x:Key="ValueDividedByParameterConverter" /&gt; &lt;!-- Popup Visibility --&gt; &lt;BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /&gt; &lt;Style x:Key="PopupPanelContentStyle" TargetType="{x:Type Grid}"&gt; &lt;Setter Property="Grid.Visibility" Value="{Binding Path=IsPopupVisible, RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}, Converter={StaticResource BooleanToVisibilityConverter}}"/&gt; &lt;/Style&gt; &lt;/ControlTemplate.Resources&gt; &lt;Grid x:Name="PopupPanelContent" Style="{StaticResource PopupPanelContentStyle}"&gt; &lt;Grid.Resources&gt; &lt;!-- Storyboard to show Content --&gt; &lt;Storyboard x:Key="ShowEditPanelStoryboard" SpeedRatio="5"&gt; &lt;DoubleAnimation Storyboard.TargetName="PopupPanelContent" Storyboard.TargetProperty="RenderTransform.(ScaleTransform.ScaleX)" From="0.00" To="1.00" Duration="00:00:01" /&gt; &lt;DoubleAnimation Storyboard.TargetName="PopupPanelContent" Storyboard.TargetProperty="RenderTransform.(ScaleTransform.ScaleY)" From="0.00" To="1.00" Duration="00:00:01" /&gt; &lt;/Storyboard&gt; &lt;/Grid.Resources&gt; &lt;!-- Setting up RenderTransform for Popup Animation --&gt; &lt;Grid.RenderTransform&gt; &lt;ScaleTransform CenterX="{Binding Path=PopupParent.ActualWidth, Converter={StaticResource ValueDividedByParameterConverter}, ConverterParameter=2, RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}}" CenterY="{Binding Path=PopupParent.ActualHeight, Converter={StaticResource ValueDividedByParameterConverter}, ConverterParameter=2, RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}}" /&gt; &lt;/Grid.RenderTransform&gt; &lt;!-- Grayscale background &amp; prevents mouse input --&gt; &lt;Rectangle Fill="Gray" Opacity="{Binding Path=BackgroundOpacity, RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}}" Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}, Path=Height}" Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}}, Path=Width}" /&gt; &lt;!-- Popup Content --&gt; &lt;ContentControl x:Name="PopupContentControl" KeyboardNavigation.TabNavigation="Cycle" PreviewKeyDown="PopupPanel_PreviewKeyDown" PreviewLostKeyboardFocus="PopupPanel_LostFocus" IsVisibleChanged="PopupPanel_IsVisibleChanged" HorizontalAlignment="Center" VerticalAlignment="Center" &gt; &lt;ContentPresenter Content="{TemplateBinding Content}" /&gt; &lt;/ContentControl&gt; &lt;/Grid&gt; &lt;/ControlTemplate&gt; &lt;/UserControl.Template&gt; &lt;/UserControl&gt; </code></pre> <p>And the code-behind the UserControl looks like this:</p> <pre><code>using System; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; namespace PopupPanelSample { /// &lt;summary&gt; /// Panel for handling Popups: /// - Control with name PART_DefaultFocusControl will have default focus /// - Can define PopupParent to determine if this popup should be hosted in a parent panel or not /// - Can define the property EnterKeyCommand to specifify what command to run when the Enter key is pressed /// - Can define the property EscapeKeyCommand to specify what command to run when the Escape key is pressed /// - Can define BackgroundOpacity to specify how opaque the background will be. Value is between 0 and 1. /// &lt;/summary&gt; public partial class PopupPanel : UserControl { #region Fields bool _isLoading = false; // Flag to tell identify when DataContext changes private UIElement _lastFocusControl; // Last control that had focus when popup visibility changes, but isn't closed #endregion // Fields #region Constructors public PopupPanel() { InitializeComponent(); this.DataContextChanged += Popup_DataContextChanged; // Register a PropertyChanged event on IsPopupVisible DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(PopupPanel.IsPopupVisibleProperty, typeof(PopupPanel)); if (dpd != null) dpd.AddValueChanged(this, delegate { IsPopupVisible_Changed(); }); dpd = DependencyPropertyDescriptor.FromProperty(PopupPanel.ContentProperty, typeof(PopupPanel)); if (dpd != null) dpd.AddValueChanged(this, delegate { Content_Changed(); }); } #endregion // Constructors #region Events #region Property Change Events // When DataContext changes private void Popup_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { DisableAnimationWhileLoading(); } // When Content Property changes private void Content_Changed() { DisableAnimationWhileLoading(); } // Sets an IsLoading flag so storyboard doesn't run while loading private void DisableAnimationWhileLoading() { _isLoading = true; this.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(delegate() { _isLoading = false; })); } // Run storyboard when IsPopupVisible property changes to true private void IsPopupVisible_Changed() { bool isShown = GetIsPopupVisible(this); if (isShown &amp;&amp; !_isLoading) { FrameworkElement panel = FindChild&lt;FrameworkElement&gt;(this, "PopupPanelContent"); if (panel != null) { // Run Storyboard Storyboard animation = (Storyboard)panel.FindResource("ShowEditPanelStoryboard"); animation.Begin(); } } // When hiding popup, clear the LastFocusControl if (!isShown) { _lastFocusControl = null; } } #endregion // Change Events #region Popup Events // When visibility is changed, set the default focus void PopupPanel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue) { ContentControl popupControl = FindChild&lt;ContentControl&gt;(this, "PopupContentControl"); this.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(delegate() { // Verify object really is visible because sometimes it's not once we switch to Render if (!GetIsPopupVisible(this)) { return; } if (_lastFocusControl != null &amp;&amp; _lastFocusControl.Focusable) { _lastFocusControl.Focus(); } else { _lastFocusControl = FindChild&lt;UIElement&gt;(popupControl, "PART_DefaultFocusControl") as UIElement; // If we can find the part named PART_DefaultFocusControl, set focus to it if (_lastFocusControl != null &amp;&amp; _lastFocusControl.Focusable) { _lastFocusControl.Focus(); } else { _lastFocusControl = FindFirstFocusableChild(popupControl); // If no DefaultFocusControl found, try and set focus to the first focusable element found in popup if (_lastFocusControl != null) { _lastFocusControl.Focus(); } else { // Just give the Popup UserControl focus so it can handle keyboard input popupControl.Focus(); } } } } ) ); } } // When popup loses focus but isn't hidden, store the last element that had focus so we can put it back later void PopupPanel_LostFocus(object sender, RoutedEventArgs e) { DependencyObject focusScope = FocusManager.GetFocusScope(this); _lastFocusControl = FocusManager.GetFocusedElement(focusScope) as UIElement; } // Keyboard Events private void PopupPanel_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) { PopupPanel popup = FindAncester&lt;PopupPanel&gt;((DependencyObject)sender); ICommand cmd = GetPopupEscapeKeyCommand(popup); if (cmd != null &amp;&amp; cmd.CanExecute(null)) { cmd.Execute(null); e.Handled = true; } else { // By default the Escape Key closes the popup when pressed var expression = this.GetBindingExpression(PopupPanel.IsPopupVisibleProperty); var dataType = expression.DataItem.GetType(); dataType.GetProperties().Single(x =&gt; x.Name == expression.ParentBinding.Path.Path) .SetValue(expression.DataItem, false, null); } } else if (e.Key == Key.Enter) { // Don't want to run Enter command if focus is in a TextBox with AcceptsReturn = True if (!(e.KeyboardDevice.FocusedElement is TextBox &amp;&amp; (e.KeyboardDevice.FocusedElement as TextBox).AcceptsReturn == true)) { PopupPanel popup = FindAncester&lt;PopupPanel&gt;((DependencyObject)sender); ICommand cmd = GetPopupEnterKeyCommand(popup); if (cmd != null &amp;&amp; cmd.CanExecute(null)) { cmd.Execute(null); e.Handled = true; } } } } #endregion // Popup Events #endregion // Events #region Dependency Properties // Parent for Popup #region PopupParent public static readonly DependencyProperty PopupParentProperty = DependencyProperty.Register("PopupParent", typeof(FrameworkElement), typeof(PopupPanel), new PropertyMetadata(null, null, CoercePopupParent)); private static object CoercePopupParent(DependencyObject obj, object value) { // If PopupParent is null, return the Window object return (value ?? FindAncester&lt;Window&gt;(obj)); } public FrameworkElement PopupParent { get { return (FrameworkElement)this.GetValue(PopupParentProperty); } set { this.SetValue(PopupParentProperty, value); } } // Providing Get/Set methods makes them show up in the XAML designer public static FrameworkElement GetPopupParent(DependencyObject obj) { return (FrameworkElement)obj.GetValue(PopupParentProperty); } public static void SetPopupParent(DependencyObject obj, FrameworkElement value) { obj.SetValue(PopupParentProperty, value); } #endregion // Popup Visibility - If popup is shown or not #region IsPopupVisibleProperty public static readonly DependencyProperty IsPopupVisibleProperty = DependencyProperty.Register("IsPopupVisible", typeof(bool), typeof(PopupPanel), new PropertyMetadata(false, null)); public static bool GetIsPopupVisible(DependencyObject obj) { return (bool)obj.GetValue(IsPopupVisibleProperty); } public static void SetIsPopupVisible(DependencyObject obj, bool value) { obj.SetValue(IsPopupVisibleProperty, value); } #endregion // IsPopupVisibleProperty // Transparency level for the background filler outside the popup #region BackgroundOpacityProperty public static readonly DependencyProperty BackgroundOpacityProperty = DependencyProperty.Register("BackgroundOpacity", typeof(double), typeof(PopupPanel), new PropertyMetadata(.5, null)); public static double GetBackgroundOpacity(DependencyObject obj) { return (double)obj.GetValue(BackgroundOpacityProperty); } public static void SetBackgroundOpacity(DependencyObject obj, double value) { obj.SetValue(BackgroundOpacityProperty, value); } #endregion ShowBackgroundProperty // Command to execute when Enter key is pressed #region PopupEnterKeyCommandProperty public static readonly DependencyProperty PopupEnterKeyCommandProperty = DependencyProperty.RegisterAttached("PopupEnterKeyCommand", typeof(ICommand), typeof(PopupPanel), new PropertyMetadata(null, null)); public static ICommand GetPopupEnterKeyCommand(DependencyObject obj) { return (ICommand)obj.GetValue(PopupEnterKeyCommandProperty); } public static void SetPopupEnterKeyCommand(DependencyObject obj, ICommand value) { obj.SetValue(PopupEnterKeyCommandProperty, value); } #endregion PopupEnterKeyCommandProperty // Command to execute when Enter key is pressed #region PopupEscapeKeyCommandProperty public static readonly DependencyProperty PopupEscapeKeyCommandProperty = DependencyProperty.RegisterAttached("PopupEscapeKeyCommand", typeof(ICommand), typeof(PopupPanel), new PropertyMetadata(null, null)); public static ICommand GetPopupEscapeKeyCommand(DependencyObject obj) { return (ICommand)obj.GetValue(PopupEscapeKeyCommandProperty); } public static void SetPopupEscapeKeyCommand(DependencyObject obj, ICommand value) { obj.SetValue(PopupEscapeKeyCommandProperty, value); } #endregion PopupEscapeKeyCommandProperty #endregion Dependency Properties #region Visual Tree Helpers public static UIElement FindFirstFocusableChild(DependencyObject parent) { // Confirm parent is valid. if (parent == null) return null; UIElement foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i &lt; childrenCount; i++) { UIElement child = VisualTreeHelper.GetChild(parent, i) as UIElement; // This is returning me things like ContentControls, so for now filtering to buttons/textboxes only if (child != null &amp;&amp; child.Focusable &amp;&amp; child.IsVisible) { foundChild = child; break; } // recursively drill down the tree foundChild = FindFirstFocusableChild(child); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } return foundChild; } public static T FindAncester&lt;T&gt;(DependencyObject current) where T : DependencyObject { // Need this call to avoid returning current object if it is the same type as parent we are looking for current = VisualTreeHelper.GetParent(current); while (current != null) { if (current is T) { return (T)current; } current = VisualTreeHelper.GetParent(current); }; return null; } /// &lt;summary&gt; /// Looks for a child control within a parent by name /// &lt;/summary&gt; public static T FindChild&lt;T&gt;(DependencyObject parent, string childName) where T : DependencyObject { // Confirm parent and childName are valid. if (parent == null) return null; T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i &lt; childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); // If the child is not of the request child type child T childType = child as T; if (childType == null) { // recursively drill down the tree foundChild = FindChild&lt;T&gt;(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // If the child's name is set for search if (frameworkElement != null &amp;&amp; frameworkElement.Name == childName) { // if the child's name is of the request name foundChild = (T)child; break; } else { // recursively drill down the tree foundChild = FindChild&lt;T&gt;(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } } else { // child element found. foundChild = (T)child; break; } } return foundChild; } #endregion } // Converter for Popup positioning public class ValueDividedByParameterConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { double n, d; if (double.TryParse(value.ToString(), out n) &amp;&amp; double.TryParse(parameter.ToString(), out d) &amp;&amp; d != 0) { return n / d; } return 0; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload