Dynamic Margin on Window Drag

2019-06-12 04:03发布

问题:

Ok so complete rewrite of the question due to lack of replies. I want a window that is drag-able but as it's being dragged, alter the margin to extend as far as the old position of the window. I.e. Window moves right X, extend margin left X. Now I've hit a few snags such as the window having it's edges cut off for some reason. Here's my code, let me know if you can spot anything!

private void Window_LocationChanged(object sender, EventArgs e)
        {
            double TmpLeft = Math.Abs(this.Left - WinLeft);
            double TmpTop = Math.Abs(this.Top - WinTop);

        if (this.IsLoaded)
        {//depending on whether the window is moved left, right
            if (this.Left > WinLeft)
            {//depending on whether the window is moved up, down
                if (this.Top > WinTop)
                    bdr.Margin = new Thickness(TmpLeft, TmpTop, 0, 0);
                else
                    bdr.Margin = new Thickness(TmpLeft, 0, 0, TmpTop);
            }
            else
            {
                if (this.Top > WinTop)
                    bdr.Margin = new Thickness(0, TmpTop, TmpLeft+ 40, 0);
                else
                    bdr.Margin = new Thickness(0, 0, TmpLeft, TmpTop);
            }
        }
    }

private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            WinLeft = this.Left;
            WinTop = this.Top;
            bdr.Height = this.ActualHeight;//I set these because they are auto
            bdr.Width = this.ActualWidth;  //before the window opens
        }

At the moment the whole window (set window background to yellow so I can see the margin) is moving, where as I want the top left corner of the margin to remain in place. I've also noticed that the area I click on to drag the window (a border) tends to move around as well so is no longer under my click as I move the window. Hope that's clear, comment any further questions.

OLD - ONLY READ TO UNDERSTAND WHAT I'M TRYING TO DO

So I'm trying to create an application that has a pop up window with a pointer/line coming from the child window to a particular place in the parent window. I achieved this like so;

<Border Name="brdr" Margin="40,0,0,0" >
    //Content
</Border>

<Line
        Name="Pointer"
        X1="0"
        X2="40"
        Y1="55"
        Y2="50"
        StrokeThickness="2"
        Stroke="Black"
></Line>

Notice the 40 left Margin on the border that makes the window larger than it appears so that the Polygon sticks out to the left and points to the parent window (If there's a better/cooler/more elegant way of doing this, I'm all ears).

So that worked fine but now I want the pointer to be dynamic. As in, if the child window gets dragged the pointer must scale relatively to the parent window's location. I.e. it must appear as if the two windows are attached via the line. Now my plan was to record the point the child window opens on (because it opens relative to the parent window, it's correct when it initialises) and then use the difference between this and the new location point (after dragging) to find the new points that the line should be going to. My code probably says it better than I ever could...

    private void Window_LocationChanged(object sender, EventArgs e)
    {
        if (this.IsLoaded)
        {
            brdr.Margin = new Thickness(Math.Abs(this.Left - WinLeft) + 40, Math.Abs(this.Top - WinTop), 0, 0);
            Pointer.X1 = Math.Abs(this.Left - WinLeft);
            Pointer.Y1 = Math.Abs(this.Top - WinTop) + 55;
            Pointer.X2 = Math.Abs(this.Left - WinLeft) + 40;
            Pointer.Y2 = Math.Abs(this.Top - WinTop) + 50;
        }
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        WinLeft = this.Left;
        WinTop = this.Top;
    }

As you can see I have to set the window margin so that it extends to the old position. Then I reset the Line coords to the new values. All these values are calculated, like I said, by comparing the opening window coords to the current coords.

My problem is, this isn't right. Would be very impressed to see someone able to figure this out.

回答1:

i m surprised there's no code to switch from in-window coordinates to screen coordinates. In a project of mine, i had to place a window right under a Control. I used this to get the screen coordinates of the point at the middle of the control:

    Point point = MyControl.PointToScreen(new Point((MyControl.ActualWidth / 2)
                                                          , MyControl.ActualHeight));
    PresentationSource source = PresentationSource.FromVisual(MyControl);
    double dpiX = (96 * source.CompositionTarget.TransformToDevice.M11);
    double dpiY = (96 * source.CompositionTarget.TransformToDevice.M22);
    XInScreenCoordinates = ((point.X * 96 / dpiX));
    YInScreenCoordinates = ((point.Y * 96 / dpiY));

If i didn't do this, there would be an error in placement, the more to the right the more error.



回答2:

ok a small app i did :
there's MainWindow, the main window with very simple content, a line and a Button :

<Grid>
    <Line x:Name="MyLine"  Stroke="Black" StrokeThickness="4" />
    <Button Name="MyButton"                Margin="248,101,0,0"
            HorizontalAlignment="Left"   VerticalAlignment="Top"
            Content="Button" />
</Grid>

then i did another window, of small size (300*300) named TestWindow. TestWindow has no content or code behind.

the code behind for MainWindow :

public partial class MainWindow : Window
{
    public TestWindow MyTestWindow;

    public MainWindow()
    {
        InitializeComponent();
        MyTestWindow = new TestWindow();
        MyTestWindow.Show();

        MyTestWindow.LocationChanged += new EventHandler(WinLocationChanged);
        MyTestWindow.SizeChanged += new SizeChangedEventHandler    (   WinSizeChanged);
        this.LocationChanged += new EventHandler(WinLocationChanged);
        this.SizeChanged += new SizeChangedEventHandler(WinSizeChanged);
    }

    void WinSizeChanged(object sender,  SizeChangedEventArgs e)  {
         UpdateLine();         }

    void WinLocationChanged(object sender, EventArgs e)        {            
         UpdateLine();         }

    void UpdateLine()
    {
        // 1. get Window's center in in this window coordinates
        double CX_sc =0.0, CY_sc = 0.0;
          GetWindowCoordinatesInCurrentWindow(MyTestWindow, ref CX_sc, ref CY_sc);
        // 2. Get Center of target Control coordinates in this window coordinates
        Point CenterButtonPoint = MyButton.TransformToAncestor(this).Transform(new Point(MyButton.ActualWidth / 2.0, MyButton.ActualHeight / 2.0));
        //3. Change line's coord.
        MyLine.X1 = CX_sc;
        MyLine.Y1 = CY_sc;
        MyLine.X2 = CenterButtonPoint.X;
        MyLine.Y2 = CenterButtonPoint.Y;
    }

    void GetWindowCoordinatesInCurrentWindow(Window ChildWindow, ref double X, ref double Y)
    {
        Point CenterOfChildWindow = ChildWindow.PointToScreen(new Point((ChildWindow.ActualWidth / 2)
                                                    , ChildWindow.ActualHeight/2));
        Point UpperLeftOfCurrentWindow = this.PointToScreen(new Point(0, 0));
          PresentationSource source = PresentationSource.FromVisual(this);
        double dpiX = ( source.CompositionTarget.TransformToDevice.M11);
        double dpiY = (source.CompositionTarget.TransformToDevice.M22);
        X = (( CenterOfChildWindow.X -UpperLeftOfCurrentWindow.X ) /dpiX);
        Y = ( (CenterOfChildWindow.Y-UpperLeftOfCurrentWindow.Y   ) / dpiY );
    }


}

What it does is that whenever one of the window is moved or resized, it will draw a line into MainWindow that 'links' the Button to the middle of the TestWindow.

OK OK i know this is not what you want :=), since the line cannot go outside of MainWindow. But the idea is to have a transparent Popup which covers the whole screen in which you do the same thing.



回答3:

Actually found a great way of doing this. What I did was I set the child window's WindowState="Maximized" then placed everything except the Line in a Canvas and by following http://denismorozov.blogspot.ie/2008/01/drag-controls-in-wpf-using.html (great article) I could make it so that I could move around my window. I had hard coded the line so that when the window opens it's in the correct position. Since one of the points of the Line shouldn't move, it was just a matter of updating the other point in the Canvas_MouseMove event like so;

Point p = border.TransformToAncestor(this).Transform(new Point(0, 0));
Line.X2 = p.X;
Line.Y2 = p.Y + 50;

Very simple way of getting your window to display relative to the parent window if you want a line joining the two. It gives the appearance of moving the window, but your actually moving a control around a maximised transparent window.