3

我想要实现的基本上是ItemsControl在 wpf 中对数据绑定对象进行就地编辑。

myItemsControl是一个WrapPanel包含多个用户控件 ( NameControl) 实例的水平控件,它显示为带有人名的粉红色小字形。看起来像这样

图。1

通过弹出窗口,我可以显示此“名称”的编辑器(绑定对象的其他属性,例如AddressGender),这绝对可以正常工作。此时我的 XAML 将类似于

<Style x:Key="NamesStyle" TargetType="{x:Type ItemsControl}">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
            <StackPanel>
                <Button Command="{Binding EditName}" BorderThickness="0" Background="Transparent" Panel.ZIndex="1">
                    <widgets:NameControl />
                </Button>
                <Popup IsOpen="{Binding IsEditMode}"
                            PlacementTarget="{Binding ElementName=button}"
                            Margin="0 5 0 0" Placement="Relative" AllowsTransparency="True" >

                <Border Background="White" BorderBrush="DarkOrchid" BorderThickness="1,1,1,1" CornerRadius="5,5,5,5" 
                        Panel.ZIndex="100">
                    <Grid ShowGridLines="False" Margin="5" Background="White" Width="300">
                        <!-- Grid Content - just editor fields/button etc -->
                    </Grid>
                </Border>
                </Popup>
            </StackPanel>
        </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

当我点击一个看起来像的名字时给出一个输出

图2

有了这个外观,我很高兴(除了我糟糕的颜色选择!!)除了弹出窗口不随寡妇移动(调整大小/最小化/最大化)并且弹出窗口甚至高于其他窗口。

因此,解决部分问题的一种方法是将弹出位置“附加”或锁定到元素。我还没有找到一个好的/简单的/xaml 方法来做到这一点。我遇到了一些基于代码的解决方案,但我不确定我是否喜欢。它只是有点气味。

我试图实现的另一个解决方案是放弃弹出窗口并尝试模拟位于其他名称之上但位于相关名称控件之上(或之下,我不挑剔)的层/面板的行为。

我尝试了一些不同的事情,主要是围绕Panel.ZIndex在一个(网格、WrapPanel、我的主窗口顶部的一个 DockPanel)中设置控件PanelControl,但收效甚微。我已经实现了一个简单BoolToVisibilityConverter的将我的编辑器网格Visibility属性绑定到我的IsEditMode视图模型属性并且工作正常,但我不能在我的生命中安排我的元素以在ItemsControl名称上显示编辑器网格。

要执行上述操作,我只需注释掉Popup并将以下绑定添加到Border包含编辑器网格的Visibility="{Binding IsEditMode, Converter={StaticResource boolToVisibility}}"中。

所做的就是:

图3

它只显示名称下的弹出窗口,不是其他名称。

有什么帮助吗?我究竟做错了什么?

4

2 回答 2

4

对我来说,这听起来像是 AdornerLayer 的工作。

我的实现一次只显示一个“弹出窗口”,您可以通过再次单击按钮来隐藏它。但是您也可以向 ContactAdorner 添加一个小的关闭按钮,或者坚持使用您的 OK 按钮,或者使用 IsHitTestVisible 的元素填充 ContactAdorner 后面的 AdornerLayer,并通过隐藏打开的 Adorner 对点击做出反应(因此点击外面的任何地方都会关闭弹出窗口) .

编辑:应您的要求添加了小的关闭按钮。ContactAdorner 和 ContactDetailsTemplate 的变化。

您可能要添加的另一件事是从底部剪裁后重新定位装饰器(我只检查从右侧剪裁)。

在此处输入图像描述

xml:

<UserControl x:Class="WpfApplication1.ItemsControlAdorner"
                 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:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 mc:Ignorable="d" 
                 xmlns:local="clr-namespace:WpfApplication1"
                 d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:ViewModel />
    </UserControl.DataContext>

    <UserControl.Resources>
        <local:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />

        <!-- Template for the Adorner -->
        <DataTemplate x:Key="ContactDetailsTemplate" DataType="{x:Type local:MyContact}" >
            <Border Background="#BBFFFFFF" BorderBrush="DarkOrchid" BorderThickness="1" CornerRadius="5" TextElement.Foreground="DarkOrchid" >
                <Grid Margin="5" Width="300">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Full name" />
                    <TextBox Grid.Row="1" Text="{Binding FullName, UpdateSourceTrigger=PropertyChanged}" />
                    <TextBlock  Grid.Row="2" Text="Address" />
                    <TextBox Grid.Row="3" Grid.ColumnSpan="2" Text="{Binding Address}" />
                    <TextBlock Grid.Column="1" Text="Gender" />
                    <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="1" >
                        <RadioButton Content="Male" IsChecked="{Binding Gender, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Gender.Male}}" />
                        <RadioButton Content="Female" IsChecked="{Binding Gender, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Gender.Female}}" />
                    </StackPanel>
                    <Button x:Name="PART_CloseButton" Grid.Column="2" Height="16">
                        <Button.Template>
                            <ControlTemplate>
                                <Border Background="#01FFFFFF" Padding="3" >
                                    <Path Stretch="Uniform" ClipToBounds="True" Stroke="DarkOrchid" StrokeThickness="2.5" Data="M 85.364473,6.9977109 6.0640998,86.29808 6.5333398,85.76586 M 6.9926698,7.4977169 86.293043,86.79809 85.760823,86.32885"  />
                                </Border>
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                </Grid>
            </Border>
        </DataTemplate>

        <!-- Button/Item style -->
        <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}" >
            <Setter Property="Foreground" Value="White" />
            <Setter Property="FontFamily" Value="Times New Roman" />
            <Setter Property="Background" Value="#CC99E6" />
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="MinHeight" Value="24" />
            <Setter Property="Margin" Value="3,2" />
            <Setter Property="Padding" Value="3,2" />
            <Setter Property="Border.CornerRadius" Value="8" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border CornerRadius="{TemplateBinding Border.CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" Margin="{TemplateBinding Margin}" >
                            <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- ItemsControl style -->
        <Style x:Key="NamesStyle" TargetType="{x:Type ItemsControl}">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Button x:Name="button" Style="{StaticResource ButtonStyle1}" Content="{Binding FullName}" >
                            <i:Interaction.Behaviors>
                                <local:ShowAdornerBehavior DataTemplate="{StaticResource ContactDetailsTemplate}" />
                            </i:Interaction.Behaviors>
                        </Button>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>

    <Grid>
        <ItemsControl ItemsSource="{Binding MyContacts}" Style="{StaticResource NamesStyle}" />
    </Grid>

</UserControl>

ShowAdornerBehavior、ContactAdorner、EnumToBooleanConverter:

using System.Windows;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Data;
using System;
namespace WpfApplication1
{
    public class ShowAdornerBehavior : Behavior<Button>
    {
        public DataTemplate DataTemplate { get; set; }

        protected override void OnAttached()
        {
            this.AssociatedObject.Click += AssociatedObject_Click;
            base.OnAttached();
        }

        void AssociatedObject_Click(object sender, RoutedEventArgs e)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
            var contactAdorner = new ContactAdorner(this.AssociatedObject, adornerLayer, this.AssociatedObject.DataContext, this.DataTemplate);
        }
    }

    public class ContactAdorner : Adorner
    {
        private ContentPresenter _contentPresenter;
        private AdornerLayer _adornerLayer;
        private static Button _btn;
        private VisualCollection _visualChildren;

        private double _marginRight = 5;
        private double _adornerDistance = 5;
        private PointCollection _points;

        private static ContactAdorner _currentInstance;

        public ContactAdorner(Button adornedElement, AdornerLayer adornerLayer, object data, DataTemplate dataTemplate)
            : base(adornedElement)
        {
            if (_currentInstance != null)
                _currentInstance.Hide(); // hides other adorners of the same type

            if (_btn != null && _btn == adornedElement)
            {
                _currentInstance.Hide(); // hides the adorner of this button (toggle)
                _btn = null;
            }
            else
            {
                _adornerLayer = adornerLayer;
                _btn = adornedElement;

                // adjust position if sizes change
                _adornerLayer.SizeChanged += (s, e) => { UpdatePosition(); };
                _btn.SizeChanged += (s, e) => { UpdatePosition(); };

                _contentPresenter = new ContentPresenter() { Content = data, ContentTemplate = dataTemplate };

                // apply template explicitly: http://stackoverflow.com/questions/5679648/why-would-this-contenttemplate-findname-throw-an-invalidoperationexception-on
                _contentPresenter.ApplyTemplate();

                // get close button from datatemplate
                Button closeBtn = _contentPresenter.ContentTemplate.FindName("PART_CloseButton", _contentPresenter) as Button;
                if (closeBtn != null)
                    closeBtn.Click += (s, e) => { this.Hide(); _btn = null; };

                _visualChildren = new VisualCollection(this); // this is needed for user interaction with the adorner layer
                _visualChildren.Add(_contentPresenter);

                _adornerLayer.Add(this);

                _currentInstance = this;

                UpdatePosition(); // position adorner
            }
        }


        /// <summary>
        /// Positioning is a bit fiddly. 
        /// Also, this method is only dealing with the right clip, not yet with the bottom clip.
        /// </summary>
        private void UpdatePosition()
        {
            double marginLeft = 0;
            _contentPresenter.Margin = new Thickness(marginLeft, 0, _marginRight, 0); // "reset" margin to get a good measure pass
            _contentPresenter.Measure(_adornerLayer.RenderSize); // measure the contentpresenter to get a DesiredSize
            var contentRect = new Rect(_contentPresenter.DesiredSize);
            double right = _btn.TranslatePoint(new Point(contentRect.Width, 0), _adornerLayer).X; // this does not work with the contentpresenter, so use _adornedElement

            if (right > _adornerLayer.ActualWidth) // if adorner is clipped by right window border, move it to the left
                marginLeft = _adornerLayer.ActualWidth - right;

            _contentPresenter.Margin = new Thickness(marginLeft, _btn.ActualHeight + _adornerDistance, _marginRight, 0); // position adorner

            DrawArrow();
        }

        private void DrawArrow()
        {
            Point bottomMiddleButton = new Point(_btn.ActualWidth / 2, _btn.ActualHeight - _btn.Margin.Bottom);
            Point topLeftAdorner = new Point(_btn.ActualWidth / 2 - 10, _contentPresenter.Margin.Top);
            Point topRightAdorner = new Point(_btn.ActualWidth / 2 + 10, _contentPresenter.Margin.Top);

            PointCollection points = new PointCollection();
            points.Add(bottomMiddleButton);
            points.Add(topLeftAdorner);
            points.Add(topRightAdorner);

            _points = points; // actual drawing executed in OnRender
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            // Drawing the arrow
            StreamGeometry streamGeometry = new StreamGeometry();
            using (StreamGeometryContext geometryContext = streamGeometry.Open())
            {
                if (_points != null && _points.Any())
                {
                    geometryContext.BeginFigure(_points[0], true, true);
                    geometryContext.PolyLineTo(_points.Where(p => _points.IndexOf(p) > 0).ToList(), true, true);
                }
            }

            // Draw the polygon visual
            drawingContext.DrawGeometry(Brushes.DarkOrchid, new Pen(_btn.Background, 0.5), streamGeometry);

            base.OnRender(drawingContext);
        }

        private void Hide()
        {
            _adornerLayer.Remove(this);
        }

        protected override Size MeasureOverride(Size constraint)
        {
            _contentPresenter.Measure(constraint);
            return _contentPresenter.DesiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            _contentPresenter.Arrange(new Rect(finalSize));
            return finalSize;
        }

        protected override Visual GetVisualChild(int index)
        {
            return _visualChildren[index];
        }

        protected override int VisualChildrenCount
        {
            get { return _visualChildren.Count; }
        }
    }

    // http://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum
    public class EnumToBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value.Equals(parameter);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value.Equals(true) ? parameter : Binding.DoNothing;
        }
    }
}

视图模型,我的联系人:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfApplication1
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private ObservableCollection<MyContact> _myContacts = new ObservableCollection<MyContact>();
        public ObservableCollection<MyContact> MyContacts { get { return _myContacts; } set { _myContacts = value; OnPropertyChanged("MyContacts"); } }


        public ViewModel()
        {
            MyContacts = new ObservableCollection<MyContact>()
            {
                new MyContact() { FullName = "Sigmund Freud", Gender = Gender.Male },
                new MyContact() { FullName = "Abraham Lincoln", Gender = Gender.Male },
                new MyContact() { FullName = "Joan Of Arc", Gender = Gender.Female },
                new MyContact() { FullName = "Bob the Khann", Gender = Gender.Male, Address = "Mongolia" },
                new MyContact() { FullName = "Freddy Mercury", Gender = Gender.Male },
                new MyContact() { FullName = "Giordano Bruno", Gender = Gender.Male },
                new MyContact() { FullName = "Socrates", Gender = Gender.Male },
                new MyContact() { FullName = "Marie Curie", Gender = Gender.Female }
            };
        }
    }

    public class MyContact : INotifyPropertyChanged
    {
        private string _fullName;
        public string FullName { get { return _fullName; } set { _fullName = value; OnPropertyChanged("FullName"); } }

        private string _address;
        public string Address { get { return _address; } set { _address = value; OnPropertyChanged("Address"); } }

        private Gender _gender;
        public Gender Gender { get { return _gender; } set { _gender = value; OnPropertyChanged("Gender"); } }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public enum Gender
    {
        Male,
        Female
    }
于 2013-03-15T17:16:47.490 回答
2

Popup出于这些原因,我个人讨厌 WPF 的内置控件,我的解决方法是使用自定义弹出用户控件

基本上,我会将 Popup 放在一个允许其子级重叠的面板中,例如 aGrid或 a Canvas,并将其放置在它应该位于的任何内容之上。

它包括 DependencyProperties 来指定它的父面板以及它是否打开,并且是普通 VisualTree 的一部分,因此它会随着您的 Window 移动并以与任何常规 UI 元素相同的方式运行。

典型用法如下所示:

<Grid x:Name="ParentPanel">
    <ItemsControl ... />

    <local:PopupPanel Content="{Binding PopupContent}"
        local:PopupPanel.PopupParent="{Binding ElementName=ParentPanel}"
        local:PopupPanel.IsPopupVisible="{Binding IsPopupVisible}" />
</Grid>

可以在我的博客上找到 UserControl 的代码以及可下载的使用示例,但我也会在此处发布它的副本。

UserControl 的 XAML 是:

<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"
             >

    <UserControl.Template>
        <ControlTemplate TargetType="{x:Type local:PopupPanel}">
            <ControlTemplate.Resources>
                <!-- Converter to get Popup Positioning -->
                <local:ValueDividedByParameterConverter x:Key="ValueDividedByParameterConverter" />

                <!-- Popup Visibility -->
                <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
                <Style x:Key="PopupPanelContentStyle" TargetType="{x:Type Grid}">
                    <Setter Property="Grid.Visibility" Value="{Binding Path=IsPopupVisible,
                        RelativeSource={RelativeSource AncestorType={x:Type local:PopupPanel}},
                        Converter={StaticResource BooleanToVisibilityConverter}}"/>
                </Style>
            </ControlTemplate.Resources>

            <Grid x:Name="PopupPanelContent" Style="{StaticResource PopupPanelContentStyle}">
                <Grid.Resources>
                    <!-- Storyboard to show Content -->
                    <Storyboard x:Key="ShowEditPanelStoryboard" SpeedRatio="5">
                        <DoubleAnimation
                            Storyboard.TargetName="PopupPanelContent"
                            Storyboard.TargetProperty="RenderTransform.(ScaleTransform.ScaleX)"
                            From="0.00" To="1.00" Duration="00:00:01"
                            />
                        <DoubleAnimation
                            Storyboard.TargetName="PopupPanelContent"
                            Storyboard.TargetProperty="RenderTransform.(ScaleTransform.ScaleY)"
                            From="0.00" To="1.00" Duration="00:00:01"
                            />
                    </Storyboard>
                </Grid.Resources>

                <!-- Setting up RenderTransform for Popup Animation -->
                <Grid.RenderTransform>
                    <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}}}"
                        />
                </Grid.RenderTransform>

                <!-- Grayscale background & prevents mouse input -->
                <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}"
                    />

                <!-- Popup Content -->
                <ContentControl x:Name="PopupContentControl"
                                KeyboardNavigation.TabNavigation="Cycle"
                                PreviewKeyDown="PopupPanel_PreviewKeyDown"
                                PreviewLostKeyboardFocus="PopupPanel_LostFocus"
                                IsVisibleChanged="PopupPanel_IsVisibleChanged"
                                HorizontalAlignment="Center" VerticalAlignment="Center"
                                >
                    <ContentPresenter Content="{TemplateBinding Content}" />
                </ContentControl>
            </Grid>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

UserControl 背后的代码如下所示:

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
{
    /// <summary>
    /// 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.
    /// </summary>
    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 && !_isLoading)
            {
                FrameworkElement panel = FindChild<FrameworkElement>(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<ContentControl>(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 && _lastFocusControl.Focusable)
                        {
                            _lastFocusControl.Focus();
                        }
                        else
                        {
                            _lastFocusControl = FindChild<UIElement>(popupControl, "PART_DefaultFocusControl") as UIElement;

                            // If we can find the part named PART_DefaultFocusControl, set focus to it
                            if (_lastFocusControl != null && _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<PopupPanel>((DependencyObject)sender);
                ICommand cmd = GetPopupEscapeKeyCommand(popup);
                if (cmd != null && 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 => 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 &&
                     (e.KeyboardDevice.FocusedElement as TextBox).AcceptsReturn == true))
                {
                    PopupPanel popup = FindAncester<PopupPanel>((DependencyObject)sender);
                    ICommand cmd = GetPopupEnterKeyCommand(popup);
                    if (cmd != null && 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<Window>(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 < 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 && child.Focusable && 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<T>(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;
        }

        /// <summary>
        /// Looks for a child control within a parent by name
        /// </summary>
        public static T FindChild<T>(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 < 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<T>(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 && 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<T>(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)
                && double.TryParse(parameter.ToString(), out d)
                && d != 0)
            {
                return n / d;
            }

            return 0;
        }        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
于 2013-03-15T18:33:50.950 回答