Ensuring WPF Window is inside screen bounds - wpf

I restore coordinates of Window on application startup. In good-old-Windows-Forms I used System.Windows.Forms.Screen collection. Is there anything similar in WPF world?
I did notice PrimaryScreen*, VirtualScreen* parameters in System.Windows.SystemParameters. However they left me hanging since it seems to be impossible to detect whether Window is inside bounds in cases when monitors are not same size.

System.Windows.Forms.Screen works perfectly well within WPF, so I think the designers of WPF saw no advantage in replacing it with a WPF-specific version.
You'll have to do a coordinate transformation of course. Here's an easy class to do the conversion:
public class ScreenBoundsConverter
{
private Matrix _transform;
public ScreenBoundsConverter(Visual visual)
{
_transform =
PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
}
public Rect ConvertBounds(Rectangle bounds)
{
var result = new Rect(bounds.X, bounds.Y, bounds.Width, bounds.Height);
result.Transform(_transform);
return result;
}
}
Example usage:
var converter = new ScreenBoundsConverter(this);
foreach(var screen in System.Windows.Forms.Screen.AllScreens)
{
Rect bounds = converter.ConvertBounds(screen.Bounds);
...
}

Related

Keeping aspect ratio of a control

I've looked for quite a while now for a way to be able to tell a WPF control (or window) to keep a certain aspect ratio.
For a Window I found this solution, that works quite well. But since it uses the Win32 API and window handles it's not working for any WPF Controls (because as far as I know in WPF only the window itself has a handle)
For a Control one usually gets the advice to put the Control in a ViewBox, but I don't want to scale my controls, I want them to resize (and keep any border width or font size).
Other "solutions" for a Control involve any form of binding the Width to the ActualHeight or the Height to the ActualWidth, or using the SizeChanged event, but this results in heavy flickering while resizing and it's not very reliable.
In case of binding the Width to the ActualHeight you can't resize only the Width (by dragging the right border) because the ActualHeight doesn't change.
In case of the event it gets tricky when width and height change at the same time, then you'd have to change the size inside the SizeChanged event... and did I mention the flickering?
After a lot of reading and searching I came to the conclusion that the best way to force any control to keep a certain aspect ratio would be to do that inside the Measure and Arrange functions.
I found this solution that creates a Decorator control with overridden Measure and Measure functions, but that would mean to put any control that's supposed to keep it's aspect ratio inside it's own Decorator. I could live with that if I had to, but I wonder if there's a better way to do it.
So, here's my question. Is it possible to create an attached property Ratio and an attached property KeepRatio and somehow override the Measure and Arrange functions of the controls in question in the OnKeepRatioChanged and RatioChanged callbacks of the attached properties?
If you want to override Arrange/Measure methods then there is no need in attached properties. This wrapper should be fine:
public partial class RatioKeeper : UserControl
{
public static readonly DependencyProperty VerticalAspectProperty = DependencyProperty.Register(
"VerticalAspect", typeof(double), typeof(RatioKeeper), new PropertyMetadata(1d));
public static readonly DependencyProperty HorizontalAspectProperty = DependencyProperty.Register(
"HorizontalAspect", typeof(double), typeof(RatioKeeper), new PropertyMetadata(1d));
public double HorizontalAspect
{
get { return (double) GetValue(HorizontalAspectProperty); }
set { SetValue(HorizontalAspectProperty, value); }
}
public double VerticalAspect
{
get { return (double) GetValue(VerticalAspectProperty); }
set { SetValue(VerticalAspectProperty, value); }
}
public RatioKeeper()
{
InitializeComponent();
}
//arrangeBounds provides size of a host.
protected override Size ArrangeOverride(Size arrangeBounds)
{
//Calculation of a content size that wont exceed host's size and will be of the desired ratio at the same time
var horizontalPart = arrangeBounds.Width / HorizontalAspect;
var verticalPart = arrangeBounds.Height / VerticalAspect;
var minPart = Math.Min(horizontalPart, verticalPart);
var size = new Size(minPart * HorizontalAspect, minPart * VerticalAspect);
//apply size to wrapped content
base.ArrangeOverride(size);
//return size to host
return size;
}
}

Multiple thread image compositing

I'm using WPF imaging to composite text, 3d graphics and images together using a DrawingVisual. The result is then rendered into a RenderTargetBitmap and saved to disk as a jpeg. All of this happens in code with no visual window.
It's working well as long as I only try to do one thing at a time. However I'd like to see if I can speed things up by using multiple threads to do the rendering. Each thread uses an object that creates its own DrawingContext, DrawingVisual, etc. However there's clearly some shared state somewhere as I get spurious errors when I attempt to access them in parallel. Sometimes it's "the calling thread cannot access this object because another thread created it". Other times it's more evil looking NullReferenceExceptions bubbling up from the bowels of WPF when I'm adding, say, points to a 3D geometry.
Is there a way to ensure each thread stays off of each other with WPF? Or is shared state unavoidable?
Is it possible that you are accidentally using the same resources across the threads? Are there any lamda expressions or anonymous methods involved in your processing code?
Eureka. I was creating my objects within threadpool threads, which is apparently a no-no when working with WPF. Instead, all objects need to be created from dispatcher threads. Incidentally this also cleared up a horrific memory leak I hadn't discovered.
My original class definition looked like this:
public class Compositor
{
private int _width;
private int _height;
private DrawingVisual _drawingVisual;
private DrawingContext _drawingContext;
private bool _isReady = false;
public void Reset(int width, int height)
{
_width = width;
_height = height;
_drawingVisual = new DrawingVisual();
_drawingContext = _drawingVisual.RenderOpen();
_isReady = true;
}
// ... compositing methods below
}
To rectify, I had my class inherit from DispatcherObject, then used the Dispatcher property to instantiate my objects.
public class Compositor : DispatcherObject
{
private int _width;
private int _height;
private DrawingVisual _drawingVisual;
private DrawingContext _drawingContext;
private bool _isReady = false;
public void Reset(int width, int height)
{
Dispatcher.Invoke(
new Action(
() =>
{
_width = width;
_height = height;
_drawingVisual = new DrawingVisual();
_drawingContext = _drawingVisual.RenderOpen();
_isReady = true;
}));
}
// ... compositing methods below
}
From MSDN:
Objects that derive from DispatcherObject have thread affinity.
Objects that derive from Freezable are free-threaded when they are frozen. For more information, see the Freezable Objects Overview.
I have encountered similar problem when working with bitmaps, brushes and other classes that should be really used (not only created) on UI thread.
This is annoying because one would like to leverage powers of WPF rendering to parallel processing realm, but this seems to be impossible (except for some scenarios like updating WriteableBitmap with pointers).

WinForms Control.Scale Method

The code below shows a small WinForms app which includes a simple Control that draws a circle. I'm trying to understand the behavior of the Control.Scale method.
If I call the Scale method on the Control from Main, as shown in the code, it scales properly. But if I instead call Scale from Circle's constructor, no scaling occurs.
My puzzlement here no doubt indicates a gross misunderstanding on my part regarding what Scale is supposed to do. Can anyone enlighten me?
using System;
using System.Windows.Forms;
using System.Drawing;
class Program
{
[STAThread]
public static void Main()
{
var circle = new Circle(Color.Orange)
{
Size = new Size(23, 23),
Location = new Point(50, 50)
};
circle.Scale(new SizeF(3.0f, 3.0f)); // <-- scaling here works
var form = new Form();
form.Controls.Add(circle);
Application.Run(form);
}
}
class Circle : Control
{
public Circle(Color color)
{
ForeColor = color;
// Scale(new SizeF(3.0f, 3.0f)); // <-- scaling here DOESN'T work
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillEllipse(new SolidBrush(ForeColor), ClientRectangle);
}
}
The Scale() method isn't meant to do this. It is a helper method to implement the AutoScaleMode property. When your control is created by the form's InitializeComponent() method, scaling is suspended with SuspendLayout(). Which is why it has no effect in your constructor. The AutoScaleMode property value is applied when the form handle is created. Which cancels any scaling you applied.
I think you are looking for e.Graphics.ScaleTransform() in your OnPaint method. It doesn't scale the control, it scales the drawing. If you really did mean to scale the control then just change its Size property.

WPF 2010 Static map

I am creating a WPF application in .net 4.0.
Basically I want to have a map of the world (2d) where by I can place images on, depending on their country location. This map can be an image however, it would be hard to determine the co-ordinates for the location right?
Can someone provide me with the simplest way of doing this?
I would do this by placing the image/shape object in Canvas Panel (panel with coordinates system) also I would add a wpf toolkit zoomviewer to get nice panning and zooming of the map.
Then I would make a Dictionary where key is a name of the country and value is a Point where pictures should be placed.
There are many solutions to this, all depending on your design (and tastes).
In all cases, the thing to remember is that the placement of the items on the map will always be relative to the size of the map, and will need to be recalculated whenever the size of the background map is changed. This calculation will be depend on the map itself (i.e. is it a rectangle or round, is the latitude/longitude a fixed and equal grid, the layout of the map, etc.).
One possible way to set up the main window is something like this:
This assumes that there is a folder called "Resources" that contains an image called "world-map.jpg", with its Build Action set to Resource.
Then, in your code behind, you would have to have something that actually adds a new image to the map in the appropriate place.
Personally, I would probably create a class (perhaps a custom or user control) to hold the image information, including the latitude/longitude, an Image object, etc. The main window (or ViewModel) would have a collection of these objects, which would make it easier to put them in the right places when a resize occurred. For example:
public partial class Window1 : Window
{
ObservableCollection<MyMapImageClass> mapImages = new ObservableCollection<MyMapImageClass>();
public Window1()
{
InitializeComponent();
layout.SizeChanged += new SizeChangedEventHandler(layout_SizeChanged);
}
void layout_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (MyMapImageClass mapImage in mapImages)
{
Point point = CalculateImagePosition(mapImage.latitude, mapImage.longitude);
mapImage.Image.SetValue(Canvas.LeftProperty, point.X);
mapImage.Image.SetValue(Canvas.TopProperty, point.Y);
}
}
Point CalculateImagePlacement(double latitude, double longitude)
{
Point point = new Point();
// Do the calculations appropriate to your map
point.X = LongCalculation(longitude);
point.Y = LatCalculation(latitude);
}
void PlaceImage(double latitude, double longitude)
{
Image img = new Image();
ImageSourceConverter converter = new ImageSourceConverter();
string path = "pack://application:,,,/Resources/SomeImage.png";
ImageSource source = (ImageSource)converter.ConvertFromString(path);
img.Source = source;
img.Width = 10d;
Point point = CalculateImagePlacement(latitude, longitude);
img.SetValue(Canvas.LeftProperty, point.X);
img.SetValue(Canvas.TopProperty, point.Y);
layout.Children.Add(img);
MyMapImageClass mapImage = new MyMapImageClass();
mapImage.Latitude = latitude;
mapImage.Longitude = longitude;
mapImage.Image = img;
mapImages.Add(mapImage);
}
}

WPF mutli-monitor problem - WindowState

I've been trying to get my WPF application to span multiple monitors for some time now and nearly have it working.
The problem seems to arise when I set the following line:
win1.WindowState = WindowState.Maximized
This causes the application to span only the primary screen.
My code is as follows:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Window1 win1 = new Window1();
win1.WindowStartupLocation = WindowStartupLocation.Manual;
win1.Width = 2560;
win1.Height = 1024;
win1.Left = 0;
win1.Top = 0;
win1.Topmost = true;
win1.Background = new SolidColorBrush(Colors.Black);
win1.WindowStyle = WindowStyle.None;
win1.Show();
win1.Focus();
}
}
And inside of Window 1:
public partial class Window1 : Window
{
public Window1()
{
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Maximized;
}
}
This example works, but the window is not maximized, and the application borders are still visible.
Including the maximized deceleration in Application_Startup makes the monitor maximize to the primary monitor.
Why is this?
First note that the concept of "Maximized" is tied to a single monitor, so you cannot truly have a maximized window on multiple monitors. Of course in WPF you can create your own window frame and draw anything you like in it, so if you want you can certainly make the user think the window is maximized and spanning multiple screens.
Also note that it is possible to span two monitors with a single rectangular window in only two cases:
The two monitors have the same height and are configured to be side by side, or
The two monitors have the same width and are configured to be above and below.
Otherwise you will need to use two separate windows to cover the entire surfaces of both monitors, or use a large window that includes areas that aren't covered by any monitor.
Ok, here's how to get the information you'll need to position your window(s):
WPF itself does not provide a way to ask about your monitor count, resolutions, or relative positions. Fortunately we can call Win32 directly using [DllImport]. To get monitor resolutions and layouts, just:
Declare the MONITORINFO struct as a struct in C#
Declare DllImports for EnumDisplayMonitors and GetMonitorInfo, both found in User32.dll
Write a method that calls EnumDisplayMonitors and passes a delegate that gets the monitor info and returns it in a list.
Here is the basic idea:
List<MONITORINFO> GetAllMonitorInfo()
{
var result = List<MONITORINFO>();
EnumDisplayMonitors(null, null,
(hMonitor, hdcMonitor, lprcMonitor, dwData) =>
{
var info = new MONITORINFO { cbSize = Marshall.Sizeof(typeof(MONITORINFO)) };
GetMonitorInfo(hMonitor, ref info);
result.Add(info);
}, null);
return result;
}
Once you have the monitor coordinates, use an algorithm of your choice to select how many window(s) you want to create and what coordinates you want for each one. Then create the windows using explicit size(s) and location(s).
Note that you'll probably want to use rcWork as opposed to rcMonitor so you don't overwrite the start menu, etc.
Also note that in many cases some of the coordinates returned will be negative, for example if the secondary monitor is to the left of the primary monitor. This is not a problem: Just use the coordinates as given and your windows will appear in the correct places.
If you can always assume that your secondary monitor is at the same resolution as the primary, you could implement something like this:
// Use this is you are concerned about the taskbar height
Rect workArea = SystemParameters.WorkArea;
this.Width = SystemParameters.PrimaryScreenWidth * 2;
this.Height = workArea.Bottom;
this.Left = 0;
this.Top = 0;
Or:
// Use this is you don't care about the taskbar height
this.Width = SystemParameters.PrimaryScreenWidth * 2;
this.Height = SystemParameters.PrimaryScreenHeight;
this.Left = 0;
this.Top = 0;

Resources