根据父位置更新弹出位置

时间:2014-06-21 17:53:08

标签: wpf .net-3.5 popup position converter

我希望在其父级的大小发生变化时更新Popup的位置。
以下代码有效,但存在问题。
正如你所看到的,在弹出窗口内有一个大按钮(宽度为300),在文本框达到这个大小之前,它不会更新弹出位置(自己尝试一下 - 写一个超级大句子,你会看到)

<TabControl>
    <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabItem}">
                        <Grid Height="26" 
                              Background="{TemplateBinding Background}" 
                              x:Name="TabGrid">
                            <Grid.ColumnDefinitions>
                                 <ColumnDefinition Width="Auto" />
                                 <ColumnDefinition Width="Auto" />
                             </Grid.ColumnDefinitions>
                             <ContentPresenter x:Name="tabTitle" Margin="5,0" 
                                               HorizontalAlignment="Left" 
                                               VerticalAlignment="Center" 
                                               ContentSource="Header"/>
                            <StackPanel Grid.Column="1" Height="26" Margin="0,0,1,0" 
                                        HorizontalAlignment="Center" 
                                        VerticalAlignment="Center" 
                                        Orientation="Horizontal">
                                <ToggleButton x:Name="Edit" Width="16" Content="e" 
                                              ToolTip="Edit" />
                                <Popup AllowsTransparency="True" 
                                       IsOpen="{Binding IsChecked, ElementName=Edit}" 
                                        Placement="Right" 
                                        PlacementTarget="{Binding ElementName=TabGrid}"
                                        StaysOpen="False" 
                                        VerticalOffset="30" 
                                        HorizontalOffset="-20">
                                    <Grid x:Name="PopupGrid">
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto" />
                                             <RowDefinition Height="*" />
                                         </Grid.RowDefinitions>
                                         <Border Width="16" Height="3" Margin="0,0,20,0" 
                                                 HorizontalAlignment="Right" 
                                                 Panel.ZIndex="1" Background="White" />
                                        <Border Grid.Row="1" Margin="0,-2,0,0" 
                                                Background="White" 
                                                BorderBrush="{Binding TabColor}" 
                                                BorderThickness="2">
                                                <StackPanel>
                                                <TextBox Name="Text" 
                                                         Text="{Binding Content, ElementName=tabTitle, UpdateSourceTrigger=PropertyChanged}" 
                                                         Margin="10"/>
                                                <Button Width="300"/>
                                            </StackPanel>
                                         </Border>
                                     </Grid>
                                </Popup>
                             </StackPanel>
                        </Grid>
                     </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
     </TabControl.ItemContainerStyle>
    <TabItem Header="TabItem">
        <Grid Background="#FFE5E5E5"/>
    </TabItem>
    <TabItem Header="TabItem">
        <Grid Background="#FFE5E5E5"/>
    </TabItem>
</TabControl>

2 个答案:

答案 0 :(得分:1)

问题是PopUp需要一些通知来更新自己。因此,作为一种解决方法,您可以按照以下步骤进行更新。

  1. 为TextBox发送 TextChanged 事件,您可以向popUp发出一些通知以更新自己。

  2. 下一个挑战是如何从TextBox获取popUp实例。为此,我们可以将popUp存储在TextBox的 Tag 中,我们可以从代码中访问它。

  3. 导致重新计算popUp展示位置的其中一个属性是 VerticalOffset ,我们可以手动设置以强制popUp重新计算位置。


  4. 这里说的是代码(更新的XAML代码)

    <Popup x:Name="popUp" AllowsTransparency="True"
           IsOpen="{Binding IsChecked, ElementName=Edit}" Placement="Right"
           PlacementTarget="{Binding ElementName=TabGrid}" StaysOpen="False"
           VerticalOffset="30" HorizontalOffset="-20">
        <Grid x:Name="PopupGrid">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Border Width="16" Height="3" Margin="0,0,20,0" HorizontalAlignment="Right" 
                    Panel.ZIndex="1" Background="White" />
            <Border Grid.Row="1" Margin="0,-2,0,0" Background="White" BorderBrush="Black" 
                    BorderThickness="2">
                <StackPanel>
                    <TextBox Name="Text" TextChanged="Text_TextChanged"
                             Tag="{Binding ElementName=popUp}"
                             Text="{Binding Content, ElementName=tabTitle, 
                                      UpdateSourceTrigger=PropertyChanged}" Margin="10"/>
                    <Button Width="300"/>
                </StackPanel>
            </Border>
        </Grid>
    </Popup>
    

    代码背后:

    private void Text_TextChanged(object sender, TextChangedEventArgs e)
    {
        Popup popup = ((TextBox)sender).Tag as Popup;
        if (popup != null)
        {
            popup.VerticalOffset += 1;
            popup.VerticalOffset -= 1;
        }
    }
    

答案 1 :(得分:0)

我使用this question的反射解决方案为您的Popup创建了一种行为。我不确定这是否是一个正确的解决方案......我在.NET3.5上测试了实现没有任何问题。

PopupBehavior 类添加到项目中:

/// <summary>
/// Attaches alignment behavior to a Popup element.
/// </summary>
public class PopupBehavior : Behavior<Popup>
{
    #region Public fields

    public static readonly DependencyProperty HeaderWidthProperty = DependencyProperty.Register("HeaderWidth", typeof(double), typeof(PopupBehavior), new FrameworkPropertyMetadata(0.0, HeaderWidthChanged));
    public static readonly DependencyProperty PopupConnectionOffsetProperty = DependencyProperty.Register("PopupConnectionOffset", typeof(double), typeof(PopupBehavior), new FrameworkPropertyMetadata(0.0));

    #endregion Public fields

    #region Private fields

    private MethodInfo updateMethod;

    #endregion Private fields

    #region Public properties

    /// <summary>
    /// Gets or sets the Width of the control to subscribe for changes.
    /// </summary>
    public double HeaderWidth
    {
        get { return (double)GetValue(HeaderWidthProperty); }
        set { SetValue(HeaderWidthProperty, value); }
    }

    /// <summary>
    /// Gets or sets the offset of the connection visual of the popup.
    /// </summary>
    public double PopupConnectionOffset
    {
        get { return (double)GetValue(PopupConnectionOffsetProperty); }
        set { SetValue(PopupConnectionOffsetProperty, value); }
    }

    #endregion Public properties

    #region Public constructors

    /// <summary>
    /// Creates an instance of the <see cref="PopupBehavior" /> class.
    /// </summary>
    public PopupBehavior()
    {
        updateMethod = typeof(Popup).GetMethod("Reposition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    }

    #endregion Public constructors

    #region Protected methods

    /// <summary>
    /// Called after the behavior is attached to an AssociatedObject.
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        var pd = DependencyPropertyDescriptor.FromProperty(Popup.IsOpenProperty, typeof(Popup));
        pd.AddValueChanged(this.AssociatedObject, IsOpenChanged);
    }

    #endregion Protected methods

    #region Private methods

    /// <summary>
    /// The HeaderWidth property has changed.
    /// </summary>
    private static void HeaderWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var b = d as PopupBehavior;
        if (b != null)
            b.UpdateHorizontalOffset();
    }

    /// <summary>
    /// Gets the width of the associated popup.
    /// </summary>
    /// <returns>A double value; width of the popup.</returns>
    /// <remarks>
    /// This method gets the width of the popup's child, since the popup itself has a width of 0
    /// when collapsed.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Occurs when the child of the popup is not derived from FrameworkElement.
    /// </exception>
    private double GetPopupWidth()
    {
        var child = this.AssociatedObject.Child as FrameworkElement;
        if (child != null)
            return child.ActualWidth;
        else
            throw new InvalidOperationException("Child of Popup is not derived from FrameworkElement");
    }

    /// <summary>
    /// The IsOpen property of the popup has changed.
    /// </summary>
    private void IsOpenChanged(object sender, EventArgs e)
    {
        if (this.AssociatedObject.IsOpen)
            UpdateHorizontalOffset();
    }

    /// <summary>
    /// Updates the HorizontalOffset of the popup.
    /// </summary>
    private void UpdateHorizontalOffset()
    {
        if (this.AssociatedObject.IsOpen)
        {
            var offset = (GetPopupWidth() - PopupConnectionOffset) * -1;
            if (this.AssociatedObject.HorizontalOffset == offset)
                updateMethod.Invoke(this.AssociatedObject, null);
            else
                this.AssociatedObject.HorizontalOffset = offset;
        }
    }

    #endregion Private methods
}

在您的XAML中进行一些更改:

  1. Placement属性更改为“Bottom”
  2. PlacementTarget绑定到您的按钮(修改)。
  3. HorizontalOffset
  4. 中删除VerticalOffsetPopup媒体资源
  5. 分配行为
  6. 您的代码应如下所示:

    ...
    xmlns:local="clr-namespace:MyProject"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    ...
    
    <Popup
        AllowsTransparency="True"
        IsOpen="{Binding IsChecked, ElementName=Edit}"
        Placement="Bottom"
        PlacementTarget="{Binding ElementName=Edit}"
        StaysOpen="False">
        <i:Interaction.Behaviors>
            <local:PopupBehavior
                HeaderWidth="{Binding ActualWidth, ElementName=tabTitle}"
                PopupConnectionOffset="36" />
        </i:Interaction.Behaviors>
        ...
    </Popup>
    

    将“ local ”clr-namespace更改为项目的命名空间。如果尚未使用,您可能需要添加对System.Windows.Interactivity程序集的引用,我使用NuGet(Blend.Interactivity.Wpf)安装它。

    HeaderWidth纯粹订阅TabItem标题宽度的更改,PopupConnectionOffset定义从Popup右侧到左侧的偏移量'white stripe Border '。

    请注意,FrameworkElement应该可以从Popup的子值中分配。此外,由于Popup现在定位到编辑按钮而不是 TabGrid ,因此当TabItem超出父容器时它会正确对齐,从而防止出现这些情况:

    enter image description here