wpf .net-3.5 popup position converter

wpf - Actualizar la posición emergente en función de la posición principal



.net-3.5 popup (2)

Quiero actualizar la posición de mi Popup cuando el tamaño de su matriz está cambiando.
El siguiente código funciona pero hay un problema.
Como puede ver, dentro de la ventana emergente hay un botón grande (ancho 300), antes de que el cuadro de texto alcance este tamaño, no actualiza la posición emergente (pruébelo usted mismo - escriba una oración súper grande y verá)

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


El problema es que PopUp necesita alguna notificación para actualizarse. Entonces, como solución, puede seguir estos pasos para actualizarla.

  1. Enganche el evento TextChanged para TextBox donde puede generar algunas notificaciones a popUp para actualizarse.

  2. El siguiente reto sería cómo llegar a la instancia popUp desde TextBox. Para eso podemos almacenar la ventana emergente en Tag of TextBox a la que podemos acceder desde el código.

  3. Una de las propiedades que da como resultado el recálculo de la colocación de ventanas emergentes es VerticalOffset que podemos configurar manualmente para forzar que popUp vuelva a calcular la posición.

Lo que se dice aquí es el código (código XAML actualizado) :

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

Código detrás:

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


Creé un comportamiento para su Popup , usando la solución de reflexión de esta pregunta . No estoy seguro de si esa es una solución correcta para usted ... Probé la implementación en .NET3.5 sin ningún problema.

Agregue la clase PopupBehavior a su proyecto:

/// <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 }

Haga algunos cambios en su XAML:

  1. Cambie la propiedad de Placement a "Abajo" .
  2. Enlace el PlacementTarget a su botón ( Editar ).
  3. Elimine las propiedades HorizontalOffset y VerticalOffset de su Popup .
  4. Asigna el Comportamiento .

Tu código debería verse más o menos así:

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

Cambie el espacio de nombres clr " local " por el de su proyecto. Si aún no lo está usando, puede que necesite agregar una referencia al ensamble System.Windows.Interactivity , lo instalé usando NuGet ( Blend.Interactivity.Wpf ).

El HeaderWidth suscribe puramente a los cambios del ancho del encabezado del TabItem y PopupConnectionOffset define el desplazamiento desde el lado derecho del Popup al lado izquierdo del ''white stripe Border ''.

Tenga en cuenta que FrameworkElement debe ser asignable desde el valor secundario de Popup . Además, dado que Popup ahora está dirigido al botón Editar en lugar de a TabGrid , se alinea correctamente cuando los Elementos de Tabulación exceden el contenedor primario, previniendo situaciones como estas: