10

I have a custom popup (as a user control) which I load programatically. I'm not able to center it on x axis, only on vertical. The popup is not added onto an xaml file, but it is added on the root window.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using System.Windows;
using Windows.UI.Core;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace QSTLibrary.WIN8.Tools
{
    public sealed partial class CustomProgressRingPopup : UserControl
    {

        public CustomProgressRingPopup()
        {
            this.InitializeComponent();
        }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
                        "Text", 
                        typeof(string), 
                        typeof(CustomProgressRingPopup),
                        new PropertyMetadata("", OnTextChanged));


        public void OpenPopup()
        {
            this.ParentPopup.IsOpen = true;
        }

        public void ClosePopup()
        {
            this.ParentPopup.IsOpen = false;
        }

        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = d as CustomProgressRingPopup;
            var newValue = e.NewValue as string;
            if (instance != null && newValue != null)
            {
                instance.CustomTextBlock.Text = newValue;
            }
        }

        private void OnPopupLoaded(object sender, RoutedEventArgs e)
        {
            this.ParentPopup.HorizontalOffset = (Window.Current.Bounds.Width - gdChild.ActualWidth) / 2;
            this.ParentPopup.VerticalOffset = (Window.Current.Bounds.Height - gdChild.ActualHeight) / 2;
        }
    }
}

userControl xaml:

<UserControl
    x:Class="QSTLibrary.WIN8.Tools.CustomProgressRingPopup"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:QSTLibrary.WIN8.Tools"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Popup x:Name="ParentPopup" HorizontalAlignment="Center" VerticalAlignment="Center" Loaded="OnPopupLoaded">
        <Grid x:Name="gdChild" Height="auto" Width="auto" Background="Blue" Margin="20">
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <ProgressRing x:Name="CustomProgressRing" Height="40" Width="40" IsActive="true" Grid.Column="0" Margin="20"/>
            <TextBlock x:Name="CustomTextBlock" Height="auto" Width="auto" FontSize="25" Grid.Column="1" Margin="20"/>
        </Grid>
    </Popup>    
</UserControl>

Here is how I use it external:

_loginProgressRingPopup.Text = "Logging in";
_loginProgressRingPopup.OpenPopup();
Alexandru Circus
  • 5,478
  • 7
  • 52
  • 89

4 Answers4

15

I've added the LayoutUpdate callback of the popup in the cs file and works now.

private void OnLayoutUpdated(object sender, object e)
        {
            if(gdChild.ActualWidth == 0 && gdChild.ActualHeight == 0)
            {
                return;
            }

            var coordinates = ParentPopup.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0));            

            double ActualHorizontalOffset = ParentPopup.HorizontalOffset;
            double ActualVerticalOffset = ParentPopup.VerticalOffset;

            double NewHorizontalOffset = ((Window.Current.Bounds.Width - gdChild.ActualWidth) / 2) - coordinates.X;
            double NewVerticalOffset = ((Window.Current.Bounds.Height - gdChild.ActualHeight) / 2) - coordinates.Y;

            if (ActualHorizontalOffset != NewHorizontalOffset || ActualVerticalOffset != NewVerticalOffset)
            {
                this.ParentPopup.HorizontalOffset = NewHorizontalOffset;
                this.ParentPopup.VerticalOffset = NewVerticalOffset;
            }
        }

and the popup from XAML:

<Popup x:Name="ParentPopup" LayoutUpdated="OnLayoutUpdated">
Alexandru Circus
  • 5,478
  • 7
  • 52
  • 89
  • "Window.Current.Bounds" was causing my app to crash with the error "Layout cycle detected. Layout could not complete." By changing it to the Top level container I was using, it resolved it. I also changed (NewHorizontalOffset != double.NaN && (ActualHorizontalOffset != NewHorizontalOffset || ActualVerticalOffset != NewVerticalOffset)) - to check for NaN which occurred as well. – Dave Friedel Mar 09 '16 at 22:44
  • @DaveFriedel We are facing the same problem, but are not able to implement your suggestion. We have a Popup within which we are using a User Control which has this code, how should we go about solving the issue so that Cycle Layout Exception is not thrown – Teja Apr 21 '16 at 09:41
  • @Teja - can you post a sample (I hate to ask that cause it is often too complex to do so) I would recommend replacing the "Window.Current.Bounds.Width" with the name of the grid root or other object that is at the top of the stack. I found the "WIndow" was causing my issues but if I named a wrapping object, it would work. – Dave Friedel Apr 26 '16 at 20:31
  • It appears that Windows doesn't like you adjusting layout-affecting properties inside a `LayoutUpdated` handler. I suggest using the `SizeChanged` event instead, which doesn't seem to have this problem. – devios1 Nov 14 '16 at 23:18
  • 1
    also had "Layout cycle detected. Layout could not complete" problem, but fixed it by casting NewHorizontal and Vertical offsets to int – duo Feb 10 '17 at 09:25
3

Positioning a Popup in a UWP Windows Store app is not as straightforward as it could be. Here is how I ended up solving it. Rather than observing the LayoutUpdated event (which can cause a layout cycle), observe the SizeChanged event of the top-level element of your view and use this to reposition the popup it is contained within.

In my case I am positioning the dialog in the center of the window, and thus use Window.Current. You also need to convert the coordinates, because the popup will use its own relative coordinate system based on where the Popup element is actually defined in your layout:

private void MyDialog_SizeChanged(object sender, SizeChangedEventArgs e) {
    var transform = Window.Current.Content.TransformToVisual(_popup);
    Point point = transform.TransformPoint(new Point(0, 0)); // gets the window's (0,0) coordinate relative to the popup

    double hOffset = (Window.Current.Bounds.Width - this.ActualWidth) / 2;
    double vOffset = (Window.Current.Bounds.Height - this.ActualHeight) / 2;

    _popup.HorizontalOffset = point.X + hOffset;
    _popup.VerticalOffset = point.Y + vOffset;
}
devios1
  • 36,899
  • 45
  • 162
  • 260
0

You could center in XAML file:

<Popup x:Name="ParentPopup" PlacementTarget="{Binding ElementName=MainPanel}" Placement="Center" />
Butzke
  • 561
  • 1
  • 14
  • 30
  • 1
    I've got the error: The property 'PlacementTarget' was not found in type 'Popup'. Same for the 'Placement'. – Alexandru Circus Sep 27 '13 at 09:49
  • @AlexandruCircus the `PlacementTarget` is the Panel that you want to center. MainPanel exist by default, but you can center in another panel. – Butzke Sep 27 '13 at 10:17
  • 1
    OK, I understand that. But it seems that the PlacementTarget property can not be applied to the Popup element, that's what I understand from the error: "The property 'PlacementTarget' was not found in type 'Popup'." Also, it couldn't auto-complete in code when I write Placemen... it should offer auto-complete suggestion to PlacementTarget – Alexandru Circus Sep 27 '13 at 10:59
  • The project is a Windows Store apps. Maybe the PlacementTarget is not available for this kind of project. I'm a newbie in C#, .NET....I'm a java programmer – Alexandru Circus Sep 27 '13 at 11:17
  • Have you tried using only `Grid` instead of `Popup`? In `OpenPopup()` method you need to set `Visibility` of `Grid`. You also didn't need to deal with offset properties of `Popup` if you use only `Grid`. – Farhan Ghumra Sep 27 '13 at 12:31
  • I think Popup for windows store apps does not have this prop. http://msdn.microsoft.com/library/windows/apps/br227842 – Alexandru Circus Sep 27 '13 at 12:55
  • PlacementTarget is part of WPF XAML, not UWP XAML. – Wes Feb 14 '19 at 00:34
-1

I modified Butzke's answer to be more universal:

<Popup PlacementTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Placement="Center" />
FocusedWolf
  • 1,062
  • 16
  • 19