我希望在其父级的大小发生变化时更新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>
答案 0 :(得分:1)
问题是PopUp需要一些通知来更新自己。因此,作为一种解决方法,您可以按照以下步骤进行更新。
为TextBox发送 TextChanged
事件,您可以向popUp发出一些通知以更新自己。
下一个挑战是如何从TextBox获取popUp实例。为此,我们可以将popUp存储在TextBox的 Tag
中,我们可以从代码中访问它。
导致重新计算popUp展示位置的其中一个属性是 VerticalOffset
,我们可以手动设置以强制popUp重新计算位置。
这里说的是代码(更新的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中进行一些更改:
Placement
属性更改为“Bottom”。PlacementTarget
绑定到您的按钮(修改)。HorizontalOffset
。VerticalOffset
和Popup
媒体资源
您的代码应如下所示:
...
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超出父容器时它会正确对齐,从而防止出现这些情况: