Dynamic Margin on Window Drag - c#

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.

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.

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.

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.

Related

WPF SkiaSharp Canvas Click Mouse position

Note: This is a C# SkiaSharp WPF application.
When I repaint my SKCanvas (which is the full width of my app), I record the width and height. Ignore height for now. On a 4K monitor the width is recorded correctly 3840 pixels (when maximised).
When I click on the canvas far left, the mouse X position recorded is almost zero as expected.
However, when I click on the far right of the canvas 2552 is given as the X position, not something near 3840 as I expected?
This is the code I use to get the mouse position when it is clicked.
private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var pixelPosition = e.GetPosition(sender as SKElement);
// ...
}
and my XAML:
<skia:SKElement
Name="MyCanvas"
PaintSurface="MyCanvas_PaintSurface"
MouseLeftButtonDown="MyCanvas_MouseLeftButtonDown"
/>
I also checked the width/height in the SizeChanged event of MyCanvas and that does NOT report 3840 either, it reports 2552 as the width?
Any ideas why the reported width is 2552 whereas the actual width is 3840?
As #Leandro pointed out your DPI scaling is 150%. You can get the DPI scale in .NET Framework >= 4.6.2 and .NET Core >= 3.0 with the GetDpi method of VisualTreeHelper. It returns a DpiScale struct that contains the scale horizontal and vertical DPI scale factors.
private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var mainWindow = Application.Current.MainWindow;
var dpiScale = VisualTreeHelper.GetDpi(mainWindow);
var dpiScaleX = dpiScale.DpiScaleX;
var dpiScaleY = dpiScale.DpiScaleY;
var pixelPosition = e.GetPosition(sender as Canvas);
var scaledPixelPosition = new System.Windows.Point(pixelPosition.X * dpiScaleX, pixelPosition.Y * dpiScaleY);
// ...your code.
}
If you are working with a lower version of a framework without GetDpi, you can use this instead.
var mainWindow = Application.Current.MainWindow;
Matrix m = PresentationSource.FromVisual(mainWindow).CompositionTarget.TransformToDevice;
var dpiScaleX = m.M11;
var dpiScaleY = m.M22;

How to position a window on multi-monitor displays in WPF?

I'm trying to position a window in the top right corner of my secondary display. In the Window_Loaded event handler, I have the following code:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.Left = Screen.AllScreens[1].WorkingArea.Left;
this.Top = Screen.AllScreens[1].WorkingArea.Top;
}
This works perfectly well when both my displays have a scale factor of 100%, but as soon as I change the scale of the primary display, the window loads completely offscreen.
Does anyone know a way to absolutely position a window in WPF? Most of the answers I found are pre-Win8.1 and don't have to worry about scaling. I can't seem to figure out the pattern behind the Top and Left properties. Thank you for your help.
I have found a solution to this problem on CodeProject
Wpf windows on two screens
The solution is to calculate the ratio between the real resolution and the WorkingArea's resolution.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var scaleRatio = Math.Max(Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.PrimaryScreenWidth,
Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.PrimaryScreenHeight);
this.Left = Screen.AllScreens[1].WorkingArea.Left / scaleRatio ;
this.Top = Screen.AllScreens[1].WorkingArea.Top / scaleRatio ;
}
You can do the following
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var p = this.PointFromScreen(new Point(Screen.AllScreens[1].WorkingArea.X, Screen.AllScreens[1].WorkingArea.Y));
this.Left += p.X;
this.Top += p.Y;
}
I figured out that the PointFromScreen function tells you the offset of an absolute coordinate from your window. Pick the position of the top left of your display and you know how much you have to move - but critically it's in the same units as your window's Top and Bottom. This is a pretty narrow case, but it be can extrapolated to positioning in general. Hope it Helps!
Maybe I'm completly wrong, but center on a screen (multimonitor) with different dpi I used this and measured it with an ruler (no joke):
var handle = new System.Windows.Interop.WindowInteropHelper(this).Handle;
var screen = System.Windows.Forms.Screen.FromHandle(handle);
var scaleRatio = Math.Max(VisualTreeHelper.GetDpi(this).DpiScaleX, VisualTreeHelper.GetDpi(this).DpiScaleY);
this.Left = (screen.WorkingArea.Left + (screen.WorkingArea.Width - this.Width * scaleRatio) / 2) / scaleRatio;
this.Top = (screen.WorkingArea.Top + (screen.WorkingArea.Height - this.Height * scaleRatio) / 2) / scaleRatio;
And the wpf form is center on actual screen.
To put it to left side (on windows 10), then the solution is:
this.Left = screen.WorkingArea.Left/ scaleRatio - SystemParameters.ResizeFrameVerticalBorderWidth - SystemParameters.FixedFrameVerticalBorderWidth;

Prevent new window from going offscreen

If the MainWindow is too close to the edge of the screen, opening a New Window with relative positioning can go off screen.
I'd like to have it detect that it's off screen and reposition itself close to the edge, even overlapping the MainWindow. For Top, Bottom, Left and Right.
Example Project Source
https://www.dropbox.com/s/3r2guvssiakcz6f/WindowReposition.zip?dl=0
private Boolean IsWindowOpened = false;
// Info Button
//
private void buttonInfo_Click(object sender, RoutedEventArgs e)
{
MainWindow mainwindow = this;
// Start Info Window
InfoWindow info = new InfoWindow(mainwindow);
// Only Allow 1 Window Instance
if (IsWindowOpened) return;
info.ContentRendered += delegate { IsWindowOpened = true; };
info.Closed += delegate { IsWindowOpened = false; };
// Position Relative to MainWindow
info.Left = mainwindow.Left - 270;
info.Top = mainwindow.Top + 0;
// Open Info Window
info.Show();
}
Example of 1280x720 screen
MainWindow Center Screen
InfoWindow -270px Left, 0px Top
Off Screen
MainWindow Top Left of Screen
InfoWindow -270px Left, 0px Top
Reposition In Screen
MainWindow Top Left of Screen
InfoWindow -160px Left, 0px Top
To Place Dialog to Left
The quick-n-dirty way of doing this is to simply use Math.Max (i.e. the right-most value) to use the offset or 0, whichever is larger. Using System.Windows.Forms.Screen allows us to accommodate for multiple monitors.
private void btnInfoToLeft_Click(object sender, RoutedEventArgs e)
{
// Figure out which screen we're on
var allScreens = Screen.AllScreens.ToList();
var thisScreen = allScreens.SingleOrDefault(s => this.Left >= s.WorkingArea.Left && this.Left < s.WorkingArea.Right);
// Place dialog to left of window, but not past screen border
InfoWindow info= new InfoWindow();
info.Left = Math.Max(this.Left - info.Width - 10, thisScreen.WorkingArea.Left);
info.Top = Math.Max(this.Top - info.Height - 10, thisScreen.WorkingArea.Top);
info.Show();
}
Note that we use the Width of the dialog - it's ActualWidth will be 0 before it is shown on the screen.
To Place Dialog to Right
Similarly, we need to figure out the right-most boundaries of the screen, and account for the width of the main window and dialog, and take the Math.Min value (i.e. the left-most value).
private void btnInfoToRight_Click(object sender, RoutedEventArgs e)
{
// Figure out which screen we're on
var allScreens = Screen.AllScreens.ToList();
var thisScreen = allScreens.SingleOrDefault(s => this.Left >= s.WorkingArea.Left && this.Left < s.WorkingArea.Right);
// Place dialog to right of window, but not past screen border
InfoWindow info = new InfoWindow();
info.Left = Math.Min(this.Left + this.ActualWidth + 10, thisScreen.WorkingArea.Right - info.Width);
info.Top = Math.Min(this.Top + this.ActualHeight + 10, thisScreen.WorkingArea.Bottom - info.Height);
info.Show();
}
This time, we still use the Width of the dialog, but the ActualWidth of the main window, which will be the width after it has been drawn (and possibly resized).
In these examples, I've also placed the dialog above/below the main window. You can set the top of the dialog to be equal to the top of the main window, or play around to get it to line up with the bottom, etc., using this example as a guide.
There are no shortcuts for this kind of problem. You'll have to figure out the dimensions of the screen you're using, then adjust the position of the info window manually.
Take a look at this StackOverflow post: How to get the size of the current screen in WPF?

Prevent Window Boundary from Over Screen Boundary when SizeChange

How to prevent Window Boundary from over Screen Boundary when Window SizeChange in C# WPF?
(that's Restrict Window Boundary in Screen )
Use the Window's OnSizeChanged Event,
and do like this:
//get Screen's Width, Height
private double screenHeight = SystemParameters.FullPrimaryScreenHeight;
private double screenWidth = SystemParameters.FullPrimaryScreenWidth;
private void MultiToolWindow_OnSizeChanged(object sender, SizeChangedEventArgs e)
{
//when window RightBoundary over screen
if (this.Left + this.Width > screenWidth)
this.Width = screenWidth-this.Left; //shrink the width
//when window DownBoundary over screen
if (this.Top + this.Height > screenHeight)
this.Height = screenHeight-this.Top; //shrink the height
}
Note that when using this, Window's SizeToContent Property should be in Manual,
if not,
you can change it like this:
public void SomeMethod(){
//set to manual, this will invoke OnSizeChangedEvent at the same time but the shrink code won't work
this.SizeToContent = SizeToContent.Manual;
//this will invoke OnSizeChangedEvent and because now is manual the shrink code works
this.SizeToContent = SizeToContent.Manual;
}
do twice to ensure when window's original SizeToContent State is WidthAndHeight can take effect too,
first time will set it to Manual and the shrink code won't take effect,
and second time cause the state is manual so the shrink code will take effect.

Program crashes when getting canvas height or width

So I am just trying to draw a x and y axis on a canvas in a WPF window. I had the canvas in my main window, and my draw method worked, however I tried to put this canvas in a new window and my program crashed. (When in a new window) The program crashes when it reaches a call to gCanvas.Height or gCanvas.Width, but if I change these to gCanvas.ActualHeight and gCanvas.ActualWidth the program runs, but it does not draw the x and y axis. Does anyone know why the method works when the canvas is in my main window but not in my new window, here is the code :
private void draw()
{
Line xAxis = new Line();
Line yAxis = new Line();
xAxis.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
xAxis.X1 = 0;
xAxis.X2 = gCanvas.Width;
xAxis.Y1 = (gCanvas.Height / 2);
xAxis.Y2 = (gCanvas.Height / 2);
xAxis.HorizontalAlignment = HorizontalAlignment.Left;
xAxis.VerticalAlignment = VerticalAlignment.Center;
xAxis.StrokeThickness = 2;
gCanvas.Children.Add(xAxis);
yAxis.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
yAxis.X1 = (gCanvas.Width / 2);
yAxis.X2 = (gCanvas.Width / 2);
yAxis.Y1 = 0;
yAxis.Y2 = gCanvas.Height;
yAxis.HorizontalAlignment = HorizontalAlignment.Left;
yAxis.VerticalAlignment = VerticalAlignment.Center;
yAxis.StrokeThickness = 2;
gCanvas.Children.Add(yAxis);
}
Here is the XAML for the 2nd window
<Window x:Class="Control.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="362" Width="412">
<Grid>
<Canvas Name="gCanvas" Margin="5" Background="White"/>
</Grid>
</Window>
and i call my draw function like this
public Window1()
{
InitializeComponent();
if (this.IsLoaded)
draw();
}
So, your question is
Does anyone know why the method works when the canvas is in my main window but not in my new window?
Well, no. You are the only person that can know that. However, from your problem description, I can tell that your gCanvas control has not been rendered at the time that you have called your draw method. I can tell this because your Exception was thrown at the gCanvas.Width call, so that must have had a value of Nan, or Not a Number.
The ActualWidth property will not return that value, so that is why that worked. As the shape was not drawn, I could therefore assume that the ActualWidth property returned 0. So if this is the case, then that points to the fact that you must be calling the draw method too early and if you're doing it in the constructor, then that is too early. Instead, handle the FrameworkElement.Loaded Event on the Window:
public YourWindow()
{
...
Loaded += YourWindow_Loaded;
}
private void YourWindow_Loaded(object sender, RoutedEventArgs e)
{
draw();
}

Categories