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

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;

Related

How to measure length of line which is drawn on image? C#

I would like to write an application that will measure fragments of a specimen examined under a microscope. I thought that the best way would be to capture the image and draw on selected parts of the specimen then count the value of the drawn line in pixels (and later to convert this value into the appropriate unit).
Is there anything that helps solve such issue already implemented or any tool/package or something that allows such calculations?
I will also willingly learn about solutions in other programming languages if they allow to solve this problem in a easier way or just in some way.
This is a very basic example of measuring a segmented line drawn onto an image in winforms.
It uses a PictureBox to display the image, a Label to display the current result and for good measure I added two Buttons the clear all points and to undo/remove the last one.
I collect to pixel positions in a List<Point> :
List<Point> points = new List<Point>();
The two edit buttons are rather simple:
private void btn_Clear_Click(object sender, EventArgs e)
{
points.Clear();
pictureBox1.Invalidate();
show_Length();
}
private void btn_Undo_Click(object sender, EventArgs e)
{
if (points.Any())points.Remove(points.Last());
pictureBox1.Invalidate();
show_Length();
}
Note how I trigger the Paint event by invalidating the image whenever the points collection changes..
The rest of the code is also simple; I call a function to calculate and display the sum of all segment lengths. Note that I need at least two points before I can do that or display the first line..
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
points.Add(e.Location);
pictureBox1.Invalidate();
show_Length();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (points.Count > 1) e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
void show_Length()
{
lbl_len.Text = (pointsF.Count) + " point(s), no segments. " ;
if (!(points.Count > 1)) return;
double len = 0;
for (int i = 1; i < points.Count; i++)
{
len += Math.Sqrt((points[i-1].X - points[i].X) * (points[i-1].X - points[i].X)
+ (points[i-1].Y - points[i].Y) * (points[i-1].Y - points[i].Y));
}
lbl_len.Text = (points.Count-1) + " segments, " + (int) len + " pixels";
}
A few notes:
The image is displayed without any zooming. PictureBox has a SizeMode property to make zoomed display simple. In such a case I recommend to store not the direct pixel locations of the mouse but 'unzoomed' values and to use a 'rezoomed' list of values for the display. This way you can zoom in and out and still have the points stick to the right spots.
For this you ought to use a List<PointF> to keep precision.
When zooming e.g. by enlarging the PictureBox, maybe after nesting it in a Panel, make sure to either keep the aspect ratio equal to that of the Image or to do a full calculation to include the extra space left or top; in SizeMode.Normal the image will always sit flush TopLeft but in other modes it will not always do so.
For the calculation of actual i.e. physical distances simply divide by the actual dpi value.
Let's see what we have in action:
Update:
To get a chance to create cloers fits and better precision we obviously need to zoom in on the image.
Here are the necessary changes:
We add a list of 'floating points':
List<PointF> pointsF = new List<PointF>();
And use it to store the un-zoomed mouse positions in the mouse down:
pointsF.Add( scaled( e.Location, false));
We replace all other occurances of points with pointsF.
The Paint event always calculates the scaled points to the current zoom level:
if (pointsF.Count > 1)
{
points = pointsF.Select(x => Point.Round(scaled(x, true))).ToList();
e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
And the function to do the scaling looks like this:
PointF scaled(PointF p, bool scaled)
{
float z = scaled ? 1f * zoom : 1f / zoom;
return new PointF(p.X * z, p.Y * z);
}
It uses a class level variable float zoom = 1f; which gets set along with the picturebox's Clientsize in the Scroll event of a trackbar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
List<float> zooms = new List<float>()
{ 0.1f, 0.2f, 0.5f, 0.75f, 1f, 2, 3, 4, 6, 8, 10};
zoom = zooms[trackBar1.Value];
int w = (int)(pictureBox2.Image.Width * zoom);
int h = (int)(pictureBox2.Image.Height * zoom);
pictureBox2.ClientSize = new Size(w, h);
lbl_zoom.Text = "zoom: " + (zoom*100).ToString("0.0");
}
The picturebox is nested inside a Panel with AutoScroll on. Now we can zoom and scroll while adding segments:

Winforms to start center (in terms of height) but to the left of the screen

I'm trying to adapt #HansPasant's code from vb.net to c#. Also I want to adapt it so the winForms starts centered in terms of top-to-bottom but to the far left of the screen in terms of left-to-right:
vb.net from here How to set winform start position at top right? :
Public Class Form1
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
Dim scr = Screen.FromPoint(Me.Location)
Me.Location = New Point(scr.WorkingArea.Right - Me.Width, scr.WorkingArea.Top)
MyBase.OnLoad(e)
End Sub
End Class
My current (bad) attempt:
private void scriptSurfer_Load(object sender,EventArgs e)
{
var scr = Screen.FromPoint(this.Location);
this.Location = New Point(scr.WorkingArea.Left - this.Width, scr.WorkingArea.Top);
this.OnLoad(e);
}
If I have understood your question you need
The LeftMost position of the Screen = 0
Vertical Centering = (Screen.Height - form.Height) / 2
this.Location = new Point(0, (scr.WorkingArea.Height - this.Height) / 2);
and do not forget
Form.StartPosition = FormStartPosition.Manual
As noted below in comments, the best point in which execute this code is inside the override of the OnLoad method albeit a Load event should work just fine in 99% of the situations. Also using the Screen.WorkingArea.Left property to position the form on the left side of the screen could be better instead of a fixed leftmost position. This could avoid edge cases where the leftmost available position is not at zero coordinates.
protected override void OnLoad(EventArgs e)
{
var scr = Screen.PrimaryScreen;
this.Location = new Point(scr.WorkingArea.Left,
(scr.WorkingArea.Height - this.Height) / 2);
base.OnLoad(e);
}

Setting the location of WPF window dynamically when we change the screen resolution from low to high

When we change screen resolution the WPF window not showing on proper location(Bottom Right).
1.Change screen resolution from high to lower value.
2.Open the WPF window.
3.Again change screen resolution from low to high.
The window will not display on proper location it is going Up .
I want it to again at bottom right.
How can I fix this problem?
You will have to move the window after resolution change using your own code I believe, something like this:
window.Left = SystemParameters.PrimaryScreenWidth - window.Width;
window.Top = = SystemParameters.PrimaryScreenHeight - window.Height;
Check this post to see, how to detect screen resolution change
http://social.msdn.microsoft.com/Forums/en-US/fc2f6dfa-f22c-477e-b3a5-54a088176932/detecting-screen-resolution-change
So the whole code would be like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
}
void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
this.Left = SystemParameters.PrimaryScreenWidth - this.Width;
this.Top = SystemParameters.PrimaryScreenHeight - this.Height;
}
}

Dynamic Margin on Window Drag

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.

How to convert screen coordinates to form relative coordinates (winforms)?

I have the following function (that is incorrect):
private void TreeView_DragDrop(object sender, DragEventArgs e)
{
TreeNode CurrentNode =
TreeView.GetNodeAt(e.X - this.Left - NotesView.Left,
e.Y - this.Top - NotesView.Top);
// [snip]...
}
But this is incorrect because it doesn't take into account the forms decorations... I'm sure there has to be a better way to do this other than hard coding it (which'll be wrong anyway, depending on several things such as Vista vs XP vs Win2k), but I can't find it.
You can use:
Point clientPoint = TreeView.PointToClient( new Point( e.X, e.Y ) );
to get relative coordinates.

Categories