Search code examples
c#silverlightmdichildchildwindowmdiparent

How to limit child window's movement within parent boundaries?


I was wondering is it possible to limit child window's ability to be moved around to only within parent's panel boundaries ? Suppose I create a child window with a button click:

<UserControl x:Class="ChildWindowTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Child="clr-namespace:ChildWindowTest"
mc:Ignorable="d"             
d:DesignHeight="300" d:DesignWidth="400" >

<Grid x:Name="LayoutRoot" >
    <StackPanel Width="500" Height="500">
        <Button Width="100" Height="25" Click="Button_Click" Content="Child Window"/>
    </StackPanel>
</Grid>
</UserControl>

<controls:ChildWindow 
x:Class="ChildWindowTest.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="400"     Height="300"    Title="ChildWindow1" >

<Grid x:Name="LayoutRoot" Margin="2">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

</Grid>
</controls:ChildWindow>

I can move the generated child to left, right and down off screen (clipped off). I want to avoid that, basically set up a boundary within which child window is allowed to be moved (within StackPanel boundary)

Thank you for any suggestion ..


Solution

  • I tried to keep my solution as simple as possible, but still it was quite challenging to achieve the desired behaviour.

    Basically, if you leave the ChildWindow's ControlTemplate alone, the following code should work for you:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ChildWindow wnd = new ChildWindow();
        wnd.Width = 800;
        wnd.Height = 600;
        wnd.Title = "Test";
    
        wnd.MouseLeftButtonUp += wnd_MouseLeftButtonUp;
        wnd.Loaded += wnd_Loaded;
    
        wnd.Show();
    }
    
    private void wnd_Loaded(object sender, RoutedEventArgs e)
    {
        var wnd = sender as ChildWindow;
        myApplicationActualWidth = Application.Current.Host.Content.ActualWidth;
        myApplicationActualHeight = Application.Current.Host.Content.ActualHeight;
    
        //This call might be necessary to make sure the visual tree of wnd is constructed and can be inspected
        wnd.UpdateLayout();
    
        //wnd is guaranteed to have at least one child here
        myRoot = (FrameworkElement)VisualTreeHelper.GetChild(wnd, 0);
        myContentRoot = myRoot.FindName("ContentRoot") as FrameworkElement;
    
        //this is the title bar part
        myChrome = myRoot.FindName("Chrome") as FrameworkElement;
        myChrome.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(wnd_MouseLeftButtonUp), true);
    
        myTransform = (myContentRoot.RenderTransform as TransformGroup).Children.OfType<TranslateTransform>().First();
    }
    
    private void wnd_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        Point rootMousePosition = e.GetPosition(sender as ChildWindow);
        Point contentRootMousePosition = e.GetPosition(myContentRoot);
        Point currentOffset = new Point(rootMousePosition.X - contentRootMousePosition.X, rootMousePosition.Y - contentRootMousePosition.Y);
        TransformChildWindowToValidPosition(currentOffset);
    }
    
    private void TransformChildWindowToValidPosition(Point currentPosition)
    {
        // handle left side
        if (currentPosition.X < 0)
        {
            myTransform.X = myTransform.X - currentPosition.X;
        }
    
        // handle top
        if (currentPosition.Y < 0)
        {
            myTransform.Y = myTransform.Y - currentPosition.Y;
        }
    
        // handle right side
        if (currentPosition.X + myContentRoot.ActualWidth > ActualWidth)
        {
            myTransform.X = myTransform.X - (currentPosition.X + myContentRoot.ActualWidth - myApplicationActualWidth);
        }
    
        // handle bottom
        if (currentPosition.Y + myContentRoot.ActualHeight > ActualHeight)
        {
            myTransform.Y = myTransform.Y - (currentPosition.Y + myContentRoot.ActualHeight - myApplicationActualHeight);
        }
    }
    
    private TranslateTransform myTransform;
    private FrameworkElement myRoot;
    private FrameworkElement myContentRoot;
    private FrameworkElement myChrome;
    private double myApplicationActualWidth;
    private double myApplicationActualHeight;
    

    This code basically does not allow you the move your ChildWindow outside the boundaries of the current SL application, but it should be a piece of cake to get your hands on the dimensions of the "parent" control of the window and readjust the values for all the edges.

    Hope I could help...