Search code examples
c#wpfpopupmouseeventmousemove

How to make Popup movable in c# WPF more than once?


I have created a usercontrol which uses

        <UserControl .......           
         MouseMove="UserControl_MouseMove" 
         MouseLeftButtonUp="UserControl_MouseLeftButtonUp">

My PopUp section of the code:

<Popup Name="CustomTablePopup" Placement="Center" IsOpen="False" PopupAnimation="Slide" MouseLeftButtonDown="CustomTablePopup_MouseLeftButtonDown" AllowsTransparency="True">
        <Border x:Name="roundBorder" BorderThickness="0" CornerRadius="10" Background="Yellow">
          <Border.Effect>
            <DropShadowEffect Color="Black" Direction="-360" ShadowDepth="40" Opacity="0.7" />
          </Border.Effect>
          
          <Grid Width="Auto" Height="Auto" Background="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <!-- PopUp Header -->
            <!-- Style for the Border Colour -->
            <Rectangle Grid.Row="0" Fill="Gold" Opacity="0.8"/>
            <Grid x:Name="GridHeader" Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="250"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="200"/>
                </Grid.ColumnDefinitions>

                <!-- PopUp Window Buttons -->
                <Border Grid.Column="3" HorizontalAlignment="Right">
                    <Grid Background="Gold" Opacity="0.8">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>

                        <Button Style="{DynamicResource HeaderButtonStyle}" Grid.Column="2" Background="Transparent" BorderBrush="Transparent" Click="CloseButton_Click">
                            <StackPanel Orientation="Horizontal">
                                <Image RenderOptions.BitmapScalingMode="HighQuality" Source="/Resources/close-window-image.png"/>
                            </StackPanel>
                        </Button>
                    </Grid>
                </Border>
            </Grid>

            <Grid Grid.Row="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <Label Grid.Row="1" Content="This is the Custom Table Popup"/>
            </Grid>
            
            </Grid>
        </Border>
</Popup>

and I have created a singleton class which only updates once the parameter changes:

public class PopUpMovement
{
    private PopUpMovement() { }

    private static PopUpMovement _instance;
    private static readonly object _instanceLock = new object();

    public static PopUpMovement Instance()
    {
        if (_instance == null)
        {
            lock (_instanceLock)
            {
                if (_instance == null)
                {
                    _instance = new PopUpMovement();
                }
            }
        }

        return _instance;
    }

    public bool draggingPopup { get; set; }
    public bool CustomTablePopupClicked { get; set; }
}

My code in the code behind of usercontrol:

    private Point startPoint;
    private double initialHorizontalOffset;
    private double initialVerticalOffset;
    private Point initialMousePosition;
    private Point startDragPoint;
    //private bool CustomTablePopupClicked;

    public UserControlPage()
    {
        InitializeComponent();
        CustomTablePopup.MouseMove += UserControl_MouseMove;
        CustomTablePopup.MouseLeftButtonDown += CustomTablePopup_MouseLeftButtonDown;
        CustomTablePopup.MouseLeftButtonUp += UserControl_MouseLeftButtonUp;
    } 

    private void CloseButton_Click(object sender, RoutedEventArgs e)
    {
        if (CustomTablePopup.IsOpen == true)
        {
            CustomTablePopup.IsOpen = false;
        }
    }

    private void UserControl_MouseMove(object sender, MouseEventArgs e)
    {

        if (PopUpMovement.Instance().draggingPopup)
        {
            else if (PopUpMovement.Instance().CustomTablePopupClicked)
            {
                Point currentMousePosition = e.GetPosition(this); // Get the mouse position relative to the UserControl
                double offsetX = currentMousePosition.X - initialMousePosition.X;
                double offsetY = currentMousePosition.Y - initialMousePosition.Y;

                CustomTablePopup.HorizontalOffset += offsetX;
                CustomTablePopup.VerticalOffset += offsetY;

                initialMousePosition = currentMousePosition; // Update initial position
            }
        }
    }

    private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if(PopUpMovement.Instance().draggingPopup == true)
        {
            PopUpMovement.Instance().draggingPopup = false;
            PopUpMovement.Instance().CustomTablePopupClicked = false;
            Mouse.Capture(null);
        }
    }


    private void CustomTablePopup_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // Store the mouse position
        //draggingPopup = true;
        //PopUpMovement popUpMovement = PopUpMovement.Instance();
        //popUpMovement.draggingPopup = true;
        PopUpMovement.Instance().draggingPopup = true;
        initialMousePosition = e.GetPosition(this); // Get the mouse position relative to the Window
        Mouse.Capture(this); // Capture mouse at the UserControl level
    }

It works fine if the user moves the popup window for the first time, however, if the user wants to move it again nothing happens it stays fixed. So, How can I fix the code so that the user can move the popup box anywhere the user wants?


Solution

  • It's not clear to me what your snippets are doing. And due to the lack of details I'm not able to review and fix your code so I decided to provide you with a general solution that couldn't get any simpler and also eliminates the pointless and ugly Singleton.

    To make a Popup draggable you first should attach the event handlers directly to the Popup. Then ensure a drag target by wrapping the content of the Popup into a Border with the Border.Background set to Transparent (or any other brush. Just ensure the value is not null). If you have Popup.AllowsTransparency set to true then a Transparent background is not an option.

    Then you must filter the clicked element: for example, you don't want to drag the Popup if the user clicks into a TextBox. Instead, you would want to allow the TextBox to select text.

    <UserControl>
      <Popup IsOpen="True"
             PreviewMouseDown="Popup_PreviewMouseDown"
             PreviewMouseMove="Popup_PreviewMouseMove">
        <Border Background="White"
                Padding="8">
          <TextBox Text="I'm a Popup" />
        </Border>
      </Popup>
    </UserControl>
    
        private Point lastDragPosition;
        private bool isDragging;
        
    private void Popup_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
      this.isDragging = e.OriginalSource is FrameworkElement clickedElement 
        && clickedElement.Parent is Popup;
      if (!this.isDragging)
      {
        return;
      }
    
      this.lastDragPosition = e.GetPosition(this);
    }
    
    private void Popup_PreviewMouseMove(object sender, MouseEventArgs e)
    {
      if (!this.isDragging 
        || e.LeftButton is MouseButtonState.Released)
      {
        return;
      }
    
      Point currentDragPosition = e.GetPosition(this);
      double horizontalOffset = currentDragPosition.X - this.lastDragPosition.X;
      double verticalOffset = currentDragPosition.Y - this.lastDragPosition.Y;
      this.lastDragPosition = currentDragPosition;
    
      var popup = (Popup)sender;
      popup.HorizontalOffset += horizontalOffset;
      popup.VerticalOffset += verticalOffset;
    }