I am trying to reposition a form to bottom/right point of a control.
public void SetAutoLocation()
{
Rect rect;
GetWindowRect(referenceControl.Handle, out rect);
Point targetPoint;
targetPoint = new Point(rect.left, rect.top + referenceControl.Height);
if (rect.left + referenceControl.Width - this.Width < 0) //Outside left border
{
targetPoint.X = 0;
}
else
{
targetPoint.X = rect.left - this.Width + referenceControl.Width;
}
if (targetPoint.X + this.Width > System.Windows.Forms.SystemInformation.WorkingArea.Right) //Outside right border
{
targetPoint.X = System.Windows.Forms.SystemInformation.WorkingArea.Right - this.Width;
}
else if (targetPoint.X < 0)
targetPoint.X = 0;
if (targetPoint.Y + this.Height > System.Windows.Forms.SystemInformation.WorkingArea.Bottom) //Outside below border
{
targetPoint.Y = rect.top - this.Height;
}
if (targetPoint.Y < 0)
{
targetPoint.Y = 0;
}
if (targetPoint.X < 0)
{
targetPoint.X = 0;
}
this.Location = targetPoint;
this.Refresh();
}
The above code works well in single monitor display. But when the parent form is opened in dual monitor display, the form positions itself on the 1st monitor, as GetWindowRect() returns the rectangle inside the primary display.
So looking for some alternative to GetWindowRect() that might work on multi-display.
Use the Screen class to get the WorkingArea for the monitor that the control is on:
var screen = Screen.FromControl(referenceControl);
var area = screen.WorkingArea;
var rect = referenceControl.RectangleToScreen(
new Rectangle(0, 0, referenceControl.Width, referenceControl.Height));
// etc..
Note how RectangleToScreen can help you avoid having to pinvoke GetWindowRect().
If you consult MSDN it's clearly stated that SystemInformation.WorkingArea returns informations only for primary monitor :
WorkingArea always returns the work area of the primary monitor. If
you need the work area of a monitor in a multiple display environment,
you can call one of the overloads of Screen.GetWorkingArea.
Related
I want to keep the window's position after the window has been resized. E.g, like how JPEGView handles resizing when changing image.
In WPF this code would accomplish it:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
//Calculate half of the offset to move the window
if (sizeInfo.HeightChanged)
Top += (sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height) / 2;
if (sizeInfo.WidthChanged)
Left += (sizeInfo.PreviousSize.Width - sizeInfo.NewSize.Width) / 2;
}
How would you accomplish the same in AvaloniaUI?
Update:
I've tried subscribing to ClientSizeProperty.Changed, this just forces the window to be centered on the screen
ClientSizeProperty.Changed.Subscribe(
x =>
{
var newSize = new Size(
ClientSize.Width + (x.OldValue.Value.Width - x.NewValue.Value.Width) / 2,
ClientSize.Height + (x.OldValue.Value.Height - x.NewValue.Value.Height) / 2);
var rect = new PixelRect(
PixelPoint.Origin,
PixelSize.FromSize(newSize, scaling));
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
});
I figured it out.
ClientSizeProperty.Changed.Subscribe(size =>
{
var x = (size.OldValue.Value.Width - size.NewValue.Value.Width) / 2;
var y = (size.OldValue.Value.Height - size.NewValue.Value.Height) / 2;
Position = new PixelPoint(Position.X + (int)x, Position.Y + (int)y);
});
I am creating a Tooltip class. Generally, a rectangle will be drawn on top of the mouse position. If the rectangle hits the top side, or the right side, the rectangle would be drawn on the opposite side. This is my code for that:
private int LeavesScreen(Rectangle rectangle)
{
const int None = 0;
const int Top = 1;
const int Right = 2;
if (rectangle.Y < Viewport.Y)
return Top;
else if (rectangle.Right > Viewport.Right)
return Right;
return None;
}
public void Update(GameTime gameTime)
{
switch (LeavesScreen(Rect))
{
case 1: //Hits the top side of the screen
Destination = new Vector2(Rect.X, Rect.Y + Rect.Height);
Direction = Destination - new Vector2(Rect.X, Rect.Y);
Direction.Normalize();
break;
case 2: //Hits the right side of the screen
Destination = new Vector2(Rect.X - Rect.Width, Rect.Y);
Direction = Destination - new Vector2(Rect.X, Rect.Y);
Direction.Normalize();
break;
case 0:
Direction = Vector2.Zero;
break;
}
Position += Direction;
Rect = new Rectangle((int)Position.X, (int)Position.Y - Size.Width, Size.Width, Size.Height);
}
Doing this causes the rectangle to jump up and down, I guess this is because when the rectangle hits the edge, it gets moved, after being moved it detects that it does not hit the edge and then returns to the previous position, where it all repeats again.
NOTE:
I write this: (int)Position.Y - Size.Width so that the tooltip appears on top of the mouse, not below it.
I tried adding this inside case:0, this works, however, on the upper right half of the screen:
if (Viewport.Right - Rect.Right > Rect.Width + Viewport.Width * 0.02f)
Direction = Vector2.Zero;
if (Viewport.Top - Rect.Top < -Rect.Height - Viewport.Height * 0.02f)
Direction = Vector2.Zero;
EDIT: What's the difference if i try on the upper right or lower right side of the screen? Upper side works, lower side does not.
Note2: 0.02f is used so there is an empty space before it starts detecting collision again
Fixed the issue by taking a different approach. Instead of trying to reposition the rectangle to the opposite side of the mouse, I totally stop the rectangle from leaving the screen.
When trying to set the Vector2 Position of the tooltip, it checks if it leaves the borders.
Like this:
public void SetPosition(Vector2 newPosition)
{
//We remove Size.Height to make the tooltip appear above the mouse, not on top of it
newPosition = new Vector2(newPosition.X, newPosition.Y - Size.Height);
string LeavesBoundary = LeavesScreen(new Rectangle((int)newPosition.X, (int)newPosition.Y, (int)this.Size.Width, (int)this.Size.Height));
if (LeavesBoundary == "top")
{
this.Position = new Vector2(newPosition.X, Viewport.Top);
}
else if (LeavesBoundary == "right")
{
this.Position = new Vector2(Viewport.Right - this.Size.Width, newPosition.Y);
}
else if (LeavesBoundary == "topRight")
{
this.Position = new Vector2(Viewport.Right - this.Size.Width, Viewport.Top);
}
else
this.Position = newPosition;
}
And here is the updated LeavesScreen method:
private string LeavesScreen(Rectangle rectangle)
{
if (rectangle.Top < Viewport.Top)
{
//If the rectangle leaves the top part, create a new rectangle and see if it leaves the right part too. If it does, return "topRight"
Rectangle x = new Rectangle(rectangle.X, rectangle.Y + rectangle.Height, rectangle.Width, rectangle.Height);
if (x.Right > Viewport.Right)
return "topRight";
return "top";
}
if (rectangle.Right > Viewport.Right)
{
//Same as above, if it leaves right side, check if it leaves top side too.
Rectangle x = new Rectangle(rectangle.X - rectangle.Width, rectangle.Y, rectangle.Width, rectangle.Height);
if (x.Y < Viewport.Y)
return "topRight";
return "right";
}
return "none";
}
I'm trying to create a resizable image overlay (for cropping purposes). It seems pretty easy to resize the overlay if I ignore the aspect ratio, but I can't figure out how to perform a constrained resize that respects the AR. I figure that I obviously can't obey the overlay's "grip" positions (or even borders) unless I force the mouse to follow it, but that seems unnatural, so I'll just have to rely on the mouse gesture (which I don't mind doing).
I can also easily resize the overlay and then force it into the proper dimensions afterwards (like every other question about this topic on this site is about), but it's not very intuitive when using a mouse.
This is sort of what I'm going for:
http://deepliquid.com/projects/Jcrop/demos.php?demo=live_crop
I've written an application like this before but it was browser-based so I used a javascript library. This is a desktop application and I haven't found a suitable library for this.
I've left a lot of details out of this code snippet and simplified some conditions with booleans.
private void pbImage_Paint(object sender, PaintEventArgs e)
{
//Overlay
e.Graphics.FillRectangle(brushRect, overlayRect);
// Grips
e.Graphics.FillRectangle(gripRect, leftTopGrip);
e.Graphics.FillRectangle(gripRect, rightTopGrip);
e.Graphics.FillRectangle(gripRect, leftBottomGrip);
e.Graphics.FillRectangle(gripRect, rightBottomGrip);
AdjustGrips();
base.OnPaint(e);
}
public void AdjustGrips()
{
// The next section only causes the grips to partly obey
// the AR - the rest of the overlay ignores it
if (overlayRect.Height * arWidth <= overlayRect.Width)
overlayRect.Width = overlayRect.Height * arWidth;
else if (overlayRect.Width * arHeight <= overlayRect.Height)
overlayRect.Height = overlayRect.Width * arHeight;
leftTopGrip.X = overlayRect.Left;
leftTopGrip.Y = overlayRect.Top;
rightTopGrip.X = overlayRect.Right - rightTopGrip.Width;
rightTopGrip.Y = overlayRect.Top;
leftBottomGrip.Y = overlayRect.Bottom - leftBottomGrip.Height;
leftBottomGrip.X = overlayRect.Left;
rightBottomGrip.X = overlayRect.Right - rightBottomGrip.Width;
rightBottomGrip.Y = overlayRect.Bottom - rightBottomGrip.Height;
}
private void pbImage_MouseMove(object sender, MouseEventArgs e)
{
Point pt = new Point(e.X, e.Y);
// Details elided
if (e.Button == MouseButtons.Left && mouseinGrip)
{
if (bottomRightIsGripped)
{
newOverlayRect.X = overlayRect.X;
newOverlayRect.Y = overlayRect.Y;
newOverlayRect.Width = pt.X - newOverlayRect.Left;
newOverlayRect.Height = pt.Y - newOverlayRect.Top;
if (newOverlayRect.X > newOverlayRect.Right)
{
newOverlayRect.Offset(-width, 0);
if (newOverlayRect.X < 0)
newOverlayRect.X = 0;
}
if (newOverlayRect.Y > newOverlayRect.Bottom)
{
newOverlayRect.Offset(0, -height);
if (newOverlayRect.Y < 0)
newOverlayRect.Y = 0;
}
pbImage.Invalidate();
oldOverlayRect = overlayRect = newOverlayRect;
Cursor = Cursors.SizeNWSE;
}
// Code for other grips elided
}
AdjustGrips();
pbImage.Update();
base.OnMouseMove(e);
}
// Mouse up and down elided
You have complete control over the new size for the overlay as it drags.
The example link that you've given, is simply selecting a starting point based on the click down, then selecting Max(Abs(pt.x - start.x), Abs(pt.y - start.y)), and basing the crop square off of that.
To use a non square ratio, normalize the distances first.
// given known data
//
// Point start;
// The starting location of the mouse down for the drag,
// or the top left / bottom right of the crop based on if the mouse is
// left/above the starting point
//
// Size ratio;
// The ratio of the result crop
//
// pt = (20)x(-20)
// start = (0),(0)
// ratio = (1)x(2)
var dist = new Point(pt.X - start.X, pt.Y - start.Y);
// "normalize" the vector from the ratio
// normalized vector is the distances with respect to the ratio
// ratio is (1)x(2). A (20)x(-20) is normalized as (20),(-10)
var normalized = new Point(dist.X / ratio.Width, dist.Y / ratio.Height);
// In our (20),(-10) example, we choose the ratio's height 20 as the larger normal.
// we will base our new size on the height
var largestNormal = (Math.Abs(normalized.X) > Math.Abs(normalized.Y)
? Math.Abs(normalized.X) : Math.Abs(normalized.Y);
// The calcedX will be 20, calcedY will be 40
var calcedOffset = (largestNormal * ratio.Width, largestNormal * ratio.Height);
// reflect the calculation back to the correct quarter
// final size is (20)x(-40)
if (distX < 0) calcedOffset.X *= -1;
if (distY < 0) calcedOffset.Y *= -1;
var newPt = new Point(start.X + calcedOffset.X, start.Y + calcedOffset.Y);
Notice that one of the lengths can grow greater than the mouse location, but it will never be less. This will have the effect of the mouse traveling along the edge of the new crop box, and the box maintaining ratio.
I've figured out what was causing the original problems in my code. Unlike a static image resize, the aspect ratio code depends on which grip you're "holding", so putting it in a common location for all cases (eg. when the grip positions are set) will not work. You can easily calculate the size of the what the rect should be on the next update, but the position should be set depending on which grip is being held.
If, for example, you're resizing by holding the top left grip, then the bottom and right sides of the cropping rectangle should remain stationary. If you leave the code the same, then the rectangle resizes correctly, but it moves around the canvas and/or the grips go out of sync with the corners of the rect. There is probably a better way to do this but here's some crude code that works. I've only included code for the bottom right and top left grips to illustrate the differences. Extraneous things like setting the mouse pointer and error checking omitted.
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Point mousePosition = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
// This resizeMode, moveMode and other booleans
// are set in the MouseUp event
if (resizeBottomLeft)
{
// Top and Right should remain static!
newCropRect.X = mousePosition.X;
newCropRect.Y = currentCropRect.Y;
newCropRect.Width = currentCropRect.Right - mousePosition.X;
newCropRect.Height = mousePosition.Y - newCropRect.Top;
if (newCropRect.X > newCropRect.Right)
{
newCropRect.Offset(cropBoxWidth, 0);
if (newCropRect.Right > ClientRectangle.Width)
newCropRect.Width = ClientRectangle.Width - newCropRect.X;
}
if (newCropRect.Y > newCropRect.Bottom)
{
newCropRect.Offset(0, -cropBoxHeight);
if (newCropRect.Y < 0)
newCropRect.Y = 0;
}
// Aspect Ratio + Positioning
if (newCropRect.Width > newCropRect.Height)
{
newCropRect.Height = (int)(newCropRect.Width / ASPECT_RATIO);
}
else
{
int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
newCropRect.X = newCropRect.Right - newWidth;
newCropRect.Width = newWidth;
}
}
else if (resizeTopRight)
{
// Bottom and Left should remain static!
newCropRect.X = oldCropRect.X;
newCropRect.Y = mousePosition.Y;
newCropRect.Width = mousePosition.X - newCropRect.Left;
newCropRect.Height = oldCropRect.Bottom - mousePosition.Y;
if (newCropRect.X > newCropRect.Right)
{
newCropRect.Offset(-cropBoxWidth, 0);
if (newCropRect.X < 0)
newCropRect.X = 0;
}
if (newCropRect.Y > newCropRect.Bottom)
{
newCropRect.Offset(0, cropBoxHeight);
if (newCropRect.Bottom > ClientRectangle.Height)
newCropRect.Y = ClientRectangle.Height - newCropRect.Height;
}
// Aspect Ratio + Positioning
if (newCropRect.Width > newCropRect.Height)
{
int newHeight = (int)(newCropRect.Width / ASPECT_RATIO);
newCropRect.Y = newCropRect.Bottom - newHeight;
newCropRect.Height = newHeight;
}
else
{
int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
newCropRect.Width = newWidth;
}
}
else if (moveMode) //Moving the rectangle
{
newMousePosition = mousePosition;
int dx = newMousePosition.X - oldMousePosition.X;
int dy = newMousePosition.Y - oldMousePosition.Y;
currentCropRect.Offset(dx, dy);
newCropRect = currentCropRect;
oldMousePosition = newMousePosition;
}
if (resizeMode || moveMode)
{
oldCropRect = currentCropRect = newCropRect;
// Set the new position of the grips
AdjustGrips();
pictureBox1.Invalidate();
pictureBox1.Update();
}
}
}
I need to display a persistent grid in a TabPage. My problems would be instantly solved if I could draw to the entire non-visible portion of the TabPage and prevent graphics from being erased when scrolling.
The only other solution I can think of is tracking the scroll position in the tab and basing the grid drawn from that.
To get this to draw in the first place, I had to create an EventHandler for TabPage.Paint.
//Code removed
This method draws vertical and horizontal lines to create a grid within the visible tab, but it continues to draw whenever a Paint event occurs (i.e. scrolling), so it creates overlapping lines and aren't aligned to anything but the size of the current visible area of the tab.
Maybe something like this will work for you:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
const int gridSpacing = 20;
const int lineThickness = 1;
Bitmap bmp = new Bitmap(gridSpacing, gridSpacing);
using (System.Drawing.Pen pen = new System.Drawing.Pen(Color.Blue, lineThickness))
{
using (Graphics G = Graphics.FromImage(bmp))
{
G.Clear(this.BackColor);
G.DrawLine(pen, 0, bmp.Height - pen.Width, bmp.Width, bmp.Height - pen.Width); // horizontal
G.DrawLine(pen, bmp.Width - pen.Width, 0, bmp.Width - pen.Width, bmp.Height); // vertical
}
}
foreach (TabPage TP in tabControl1.TabPages)
{
TP.BackgroundImage = bmp;
TP.BackgroundImageLayout = ImageLayout.Tile;
}
}
}
Keep in mind that this solution is just pseudo. You also have to respond to scrolling.
void form_draw()
{
spacingX = offsetX % scale * -1;
spacingY = offsetY % scale * -1;
if (form.HorizontalPosition != lastXPosition && form.VerticalPosition == lastYPosition)
lastStartX += spacingX;
else if (tab.HorizontalScroll.Value == lastXPosition && form.VerticalPosition != lastYPosition)
lastStartY += spacingY;
lastYPosition = form.VerticalPosition;
lastXPosition = form.HorizontalPosition;
for (int i = lastStartY; i < formHeight; i += scale)
form.draw(0, i, formWidth, i);
for (int i = lastStartX; i < formWidth; i += scale)
form.draw(i, 0, i, formWidth);
}
I am using c# WinForm to develop a sman notification app. I would like to place the main form on the lower right corner of the screen working area.
In case of multiple screens, there is a way to find the rightmost screen where to place the app, or at least remember the last used screen and palce the form on its lower right corner?
I don't currently have multiple displays to check, but it should be something like
public partial class LowerRightForm : Form
{
public LowerRightForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
PlaceLowerRight();
base.OnLoad(e);
}
private void PlaceLowerRight()
{
//Determine "rightmost" screen
Screen rightmost = Screen.AllScreens[0];
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.Right > rightmost.WorkingArea.Right)
rightmost = screen;
}
this.Left = rightmost.WorkingArea.Right - this.Width;
this.Top = rightmost.WorkingArea.Bottom - this.Height;
}
}
Override the Form Onload and set the new location :
protected override void OnLoad(EventArgs e)
{
var screen = Screen.FromPoint(this.Location);
this.Location = new Point(screen.WorkingArea.Right - this.Width, screen.WorkingArea.Bottom - this.Height);
base.OnLoad(e);
}
//Get screen resolution
Rectangle res = Screen.PrimaryScreen.Bounds;
// Calculate location (etc. 1366 Width - form size...)
this.Location = new Point(res.Width - Size.Width, res.Height - Size.Height);
This following code should work :)
var rec = Screen.PrimaryScreen.WorkingArea;
int margain = 10;
this.Location = new Point(rec.Width - (this.Width + margain), rec.Height - (this.Height + margain));
int x = Screen.PrimaryScreen.WorkingArea.Right - this.Width;
int y = Screen.PrimaryScreen.WorkingArea.Bottom - this.Height;
// Add this for the real edge of the screen:
x = 0; // for Left Border or Get the screen Dimension to set it on the Right
this.Location = new Point(x, y);