I would like to draw some rectangle on top of certain area of a window in WPF application. The problem is how to do it in such way that during window's size change the figure gets resized proportionally too?
Any hints highly appreciated.
You could do something like that in your window's code behind file:
//Constructor
public MyWindow() {
InitializeComponent();
//Register the SizeChanged-event
this.SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var parentWindow = (Window)sender;
var sizeOfRectangle = CalculateSizeOfRectangle(parentWindow.actualHeight, parentWindow.actualWidth);
// assuming you have a reference to your colored rectangle here (e.g. Name defined in XAML code)
ColoredRectangle.Width = sizeOfRectangle.Width;
ColoredRectangle.Height = sizeOfRectangle.Height;
}
private Size CalculateSizeOfRectangle(double height, double width)
{
int height = ....; // Calculate height here
int width = ....; // Calculate width her
return new Size(width, height);
}
Related
I am trying to make a WPF application that can slide from the edge of the right-most screen when you hit a button (like F8). This will be similar to the Windows Notification bar in windows 10.
I am struggling finding ways to make something similar. Any help would be greatly appreciated!
I am able to make a window with a given height, width, and make it stick to the right side of screen and top as such:
public MainWindow()
{
InitializeComponent();
Width = 300;
Height = System.Windows.SystemParameters.WorkArea.Height;
Left = System.Windows.SystemParameters.WorkArea.Width - Width;
Top = 0;
}
You could override the OnPreviewKeyDown method of the window and use a DoubleAnimation to animate the Left property. Something like this:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.F8)
{
double distanceToSlide = 100;
DoubleAnimation doubleAnimation = new DoubleAnimation()
{
From = Left,
To = Left - distanceToSlide,
Duration = new Duration(TimeSpan.FromSeconds(1))
};
this.BeginAnimation(Window.LeftProperty, doubleAnimation);
}
}
The above example slides the window 100 DIP to the left during a second. You can change the properties of DoubleAnimation according to your requirements obviously.
I'm seeing some strange behaviour from WPF. I have a form with three buttons on it. One button should make the window fullscreen, one should center it on the monitor its currently on, the third button should restore the window to its normal position.
The XAML is
<Window x:Class="TestRestore.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestRestore"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen">
<Grid>
<Button Content="Max" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="94" Click="max_click" Name="max_button"/>
<Button Content="Center" HorizontalAlignment="Left" Margin="10,35,0,0" VerticalAlignment="Top" Width="94" Click="center_click" Name="center_button"/>
<Button Content="Restore" HorizontalAlignment="Left" Margin="227,143,0,0" VerticalAlignment="Top" Width="75" Click="restore_click" Name="restore_button" IsEnabled="False"/>
</Grid>
</Window>
and the code is below. The strange behaviour is that when I maximize, and then restore the window, the position is correctly restored but the window still thinks it's maximized (the maximize button looks like a restore button and you can't resize the window even though ResizeMode has been set to CanResizeWithGrip).
When the maximized window has been restored, and it thinks its still maximized even though the window position isn't maximized, just moving the window manually by dragging the title bar is enough to cause it to correct itself back to non-maximized mode.
Also, if I maximize then restore the window and then maximize it again without moving it, the maximized window position is incorrect (not in the top left).
And the mystery deepens. If I maximize then restore the window, then press alt, then press down (to get the window menu) and select 'Move' and then move the window around with the keyboard, it stays stuck in 'bogus not-mazimized mode' even though the window is being moved, so it seems the only way to unstick it is to move it with the mouse.
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace TestRestore
{
public partial class MainWindow : Window
{
WindowStyle old_window_style;
WindowState old_window_state;
double old_left;
double old_top;
double old_width;
double old_height;
public MainWindow()
{
InitializeComponent();
}
// remember position, style and state
private void SaveWindowPos()
{
old_window_style = WindowStyle;
old_window_state = WindowState;
old_left = Left;
old_top = Top;
old_width = Width;
old_height = Height;
max_button.IsEnabled = false;
center_button.IsEnabled = false;
restore_button.IsEnabled = true;
}
// put position, style and state back
private void RestoreWindowPos()
{
WindowStyle = old_window_style;
WindowState = old_window_state;
ResizeMode = ResizeMode.CanResizeWithGrip;
Left = old_left;
Top = old_top;
Width = old_width;
Height = old_height;
max_button.IsEnabled = true;
center_button.IsEnabled = true;
restore_button.IsEnabled = false;
}
// make it centered or fullscreen
private void SetActivePos(bool full_screen)
{
SaveWindowPos();
Hide();
if (full_screen)
{
ResizeMode = ResizeMode.NoResize;
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
}
else
{
Size s = new Size(800, 600);
Point p = CenterRectInMonitor(this, s);
Left = p.X;
Top = p.Y;
Width = s.Width;
Height = s.Height;
ResizeMode = ResizeMode.NoResize;
WindowState = WindowState.Normal;
}
Show();
}
private void restore_click(object sender, RoutedEventArgs e)
{
Hide();
RestoreWindowPos();
Show();
}
private void max_click(object sender, RoutedEventArgs e)
{
SetActivePos(true);
}
private void center_click(object sender, RoutedEventArgs e)
{
SetActivePos(false);
}
// interop
public const Int32 MONITOR_DEFAULTTOPRIMARY = 0x00000001;
public const Int32 MONITOR_DEFAULTTONEAREST = 0x00000002;
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(IntPtr handle, Int32 flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi);
// size of a device name string
private const int CCHDEVICENAME = 32;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MonitorInfoEx
{
public int Size;
public RectStruct Monitor;
public RectStruct WorkArea;
public uint Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string DeviceName;
public void Init()
{
this.Size = 40 + 2 * CCHDEVICENAME;
this.DeviceName = string.Empty;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RectStruct
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width
{
get
{
return Right - Left;
}
}
public int Height
{
get
{
return Bottom - Top;
}
}
}
public static MonitorInfoEx GetMonitorFromWindow(Window w)
{
var hwnd = new WindowInteropHelper(w).EnsureHandle();
var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MonitorInfoEx monitor_info = new MonitorInfoEx();
monitor_info.Init();
GetMonitorInfo(monitor, ref monitor_info);
return monitor_info;
}
// work out how a rect of 'Size size' should be centered on the monitor containing 'Window w'
public static Point CenterRectInMonitor(Window w, Size size)
{
var source = PresentationSource.FromVisual(w);
double x_scale = source.CompositionTarget.TransformToDevice.M11;
double y_scale = source.CompositionTarget.TransformToDevice.M22;
var width = size.Width * x_scale;
var height = size.Height * y_scale;
var monitor_info = GetMonitorFromWindow(w);
Size s = new Size(monitor_info.Monitor.Width, monitor_info.Monitor.Height);
Point p = new Point(monitor_info.Monitor.Left, monitor_info.Monitor.Top);
Point c = new Point(p.X + s.Width / 2, p.Y + s.Height / 2);
return new Point((c.X - width / 2) / x_scale, (c.Y - height / 2) / y_scale);
}
}
}
I don't have a complete answer for you. However you will find that your code starts working a lot better once you remove the Hide() and Show() calls.
private void restore_click(object sender, RoutedEventArgs e)
{
// Hide();
RestoreWindowPos();
// Show();
}
I'm sure you put this in to reduce flicker, but what I think is happening is that the Hide() and Show() calls are flipping the WS_VISIBLE bit in the window style word of the underlying OS window which is the same word which contains the WS_MAXIMIZE and WS_BORDER and some other things that you are manipulating. See https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
It would take more research to figure out what is exactly going on, but the fundamental problem I believe is a "leaky abstraction". Your code sets top, left, style and state as if these were independent uncoupled variables. But they are not! To set left, the OS SetWindowPos() function must be called which requires not the upper left coordinate, the window size, the Z order as well as visibility flags and whether the windows is maximized! See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx. So each time you set one of these "independent" variables you are pounding SetWindowPos(). This API call harks back to the bad old days when CPU cycles were precious and you need to pack as much functionality as possible into each API call.
Ironically this is making your code very inefficient. I think the thing to do to straighten this out is to bypass the leaking abstraction of System.Windows.Window and call SetWindowPos and possibility other API functions directly from user32.dll. Then things will be a lot more predicable.
I can't English very well yet. please understand even if you can't understand me clearly.
I have huge data table in UserControl.xaml, but downscale this UserControl object showing whole in MainWindow.
I want same size datatable showing of partially UserControl in MainWindow.
Like this image display way:
<Image>
<Image.Source>
<CroppedBitmap Source="<path to source image>" SourceRect="20,20,50,50"/>
</Image.Source>
</Image>
Showing UserControl in MainWindow like a SourceRect.
If I understand you correctly, you have several options. The first way, and I think the easiest is to use ViewBox control.
1. ViewBox
The Viewbox control inherited from Decorator is used to stretch or scale a child element, but it is scaled proportionally, ie you can not set the him size such 300x100.
Example
<Viewbox Width="300"
Height="300">
<DataGrid>
...
</DataGrid>
</ViewBox>
The second way is to use a screen capture of your control, that you would like to show, and then if you want to use CroppedBitmap.
2. Capturing screen
I found a great article by Pete Brown on this subject here:
Capturing Screen Images in WPF using GDI, Win32 and a little WPF Interop Help
In this article is an example, and it looks like this:
ScreenCapture
class ScreenCapture
{
public static BitmapSource CaptureFullScreen(bool addToClipboard)
{
return CaptureRegion(
User32.GetDesktopWindow(),
(int)SystemParameters.VirtualScreenLeft,
(int)SystemParameters.VirtualScreenTop,
(int)SystemParameters.VirtualScreenWidth,
(int)SystemParameters.VirtualScreenHeight,
addToClipboard);
}
// capture a window. This doesn't do the alt-prtscrn version that loses the window shadow.
// this version captures the shadow and optionally inserts a blank (usually white) area behind
// it to keep the screen shot clean
public static BitmapSource CaptureWindow(IntPtr hWnd, bool recolorBackground, Color substituteBackgroundColor, bool addToClipboard)
{
Int32Rect rect = GetWindowActualRect(hWnd);
Window blankingWindow = null;
if (recolorBackground)
{
blankingWindow = new Window();
blankingWindow.WindowStyle = WindowStyle.None;
blankingWindow.Title = string.Empty;
blankingWindow.ShowInTaskbar = false;
blankingWindow.AllowsTransparency = true;
blankingWindow.Background = new SolidColorBrush(substituteBackgroundColor);
blankingWindow.Show();
int fudge = 20;
blankingWindow.Left = rect.X - fudge / 2;
blankingWindow.Top = rect.Y - fudge / 2;
blankingWindow.Width = rect.Width + fudge;
blankingWindow.Height = rect.Height + fudge;
}
// bring the to-be-captured window to capture to the foreground
// there's a race condition here where the blanking window
// sometimes comes to the top. Hate those. There is surely
// a non-WPF native solution to the blanking window which likely
// involves drawing directly on the desktop or the target window
User32.SetForegroundWindow(hWnd);
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
if (blankingWindow != null)
blankingWindow.Close();
return captured;
}
// capture a region of the full screen
public static BitmapSource CaptureRegion(int x, int y, int width, int height, bool addToClipboard)
{
return CaptureRegion(User32.GetDesktopWindow(), x, y, width, height, addToClipboard);
}
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
if (addToClipboard)
{
//Clipboard.SetImage(bitmap); // high memory usage for large images
IDataObject data = new DataObject();
data.SetData(DataFormats.Dib, bitmap, false);
Clipboard.SetDataObject(data, false);
}
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
// this accounts for the border and shadow. Serious fudgery here.
private static Int32Rect GetWindowActualRect(IntPtr hWnd)
{
Win32Rect windowRect = new Win32Rect();
Win32Rect clientRect = new Win32Rect();
User32.GetWindowRect(hWnd, out windowRect);
User32.GetClientRect(hWnd, out clientRect);
int sideBorder = (windowRect.Width - clientRect.Width)/2 + 1;
// sooo, yeah.
const int hackToAccountForShadow = 4;
Win32Point topLeftPoint = new Win32Point(windowRect.Left - sideBorder, windowRect.Top - sideBorder);
//User32.ClientToScreen(hWnd, ref topLeftPoint);
Int32Rect actualRect = new Int32Rect(
topLeftPoint.X,
topLeftPoint.Y,
windowRect.Width + sideBorder * 2 + hackToAccountForShadow,
windowRect.Height + sideBorder * 2 + hackToAccountForShadow);
return actualRect;
}
}
Example of using:
private void CaptureRegionButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureRegion(100, 100, 500, 500, true);
}
private void CaptureScreenButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureFullScreen(true);
}
I want to show my window on top of the TaskBar's clock when the windows starts.
How can I find the bottom right corner location of my desktop?
I use this code that works well in windows forms app but does not work correctly in WPF:
var desktopWorkingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
this.Left = desktopWorkingArea.Right - this.Width;
this.Top = desktopWorkingArea.Bottom - this.Height;
This code works for me in WPF both with Display 100% and 125%
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
this.Left = desktopWorkingArea.Right - this.Width;
this.Top = desktopWorkingArea.Bottom - this.Height;
}
In brief I use
System.Windows.SystemParameters.WorkArea
instead of
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea
To access the desktop rectangle, you could use the Screen class - Screen.PrimaryScreen.WorkingArea property is the rectangle of your desktop.
Your WPF window has Top and Left properties as well as Width and Height, so you could set those properties relative to the desktop location.
You can use the window's SizeChanged event instead of Loaded if you want the window to stay in the corner when its size changes. This is especially handy if the window has Window.SizeToContent set to some value other than SizeToContent.Manual; in this case it will adjust to fit the content while staying in the corner.
public MyWindow()
{
SizeChanged += (o, e) =>
{
var r = SystemParameters.WorkArea;
Left = r.Right - ActualWidth;
Top = r.Bottom - ActualHeight;
};
InitializeComponent();
}
Note also that you should subtract ActualWidth and ActualHeight (instead of Width and Height as shown in some other replies) to handle more possible situations, for example switching between SizeToContent modes at runtime.
My code:
MainWindow.WindowStartupLocation = WindowStartupLocation.Manual;
MainWindow.Loaded += (s, a) =>
{
MainWindow.Height = SystemParameters.WorkArea.Height;
MainWindow.Width = SystemParameters.WorkArea.Width;
MainWindow.SetLeft(SystemParameters.WorkArea.Location.X);
MainWindow.SetTop(SystemParameters.WorkArea.Location.Y);
};
I solved this problem with a new window containing a label named MessageDisplay. The code accompanying the window was as follows:
public partial class StatusWindow : Window
{
static StatusWindow display;
public StatusWindow()
{
InitializeComponent();
}
static public void DisplayMessage( Window parent, string message )
{
if ( display != null )
ClearMessage();
display = new StatusWindow();
display.Top = parent.Top + 100;
display.Left = parent.Left + 10;
display.MessageDisplay.Content = message;
display.Show();
}
static public void ClearMessage()
{
display.Close();
display = null;
}
}
For my application, the setting of top and left puts this window below the menu on the main window (passed to DisplayMessage in the first parameter);
This above solutions did not entirely work for my window - it was too low and the bottom part of the window was beneath the taskbar and below the desktop workspace. I needed to set the position after the window content had been rendered:
private void Window_ContentRendered(object sender, EventArgs e)
{
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
this.Left = desktopWorkingArea.Right - this.Width - 5;
this.Top = desktopWorkingArea.Bottom - this.Height - 5;
}
Also, part of the frame was out of view, so I had to adjust by 5. Not sure why this is needed in my situation.
#Klaus78 's answer is correct. But since this is first thing google pops up and if working in environments where screen resolution can change often such that your app runs on virtual desktops or virtual servers and you still need it to update its placement when the screen resolution changes I have found linking to the SystemEvents.DisplaySettingsChanged event to be beneficial. Here is an example using rx and you can put this in your constructor for your view.
Observable
.FromEventPattern<EventHandler, EventArgs>(_ => SystemEvents.DisplaySettingsChanged += _, _ => SystemEvents.DisplaySettingsChanged -= _)
.Select(_ => SystemParameters.WorkArea)
.Do(_ =>
{
Left = _.Right - Width;
Top = _.Bottom - Height;
})
.Subscribe();
I am trying to position an Adorner depending on the dimensions of the parent of the adorned element. For example, I have a textbox. I want to adorn this textbox so it looks something like this:
how the adorner needs to be placed http://img707.imageshack.us/img707/9840/fig1.png
A textbox is placed in a canvas object and if there is enough space available then place the adorner (semi transparent rounded square) in line with the bottom edge of the textbox. The adorner is initiated when the user clicks on the textbox.
Currently the canvas and its contents (the textbox) is hosted in a WinForms form - so the WPF is handled by the ElementHost control.
But when I run my code, when the textbox is clicked for the first time it displays the adorner aligned to the top edge of the textbox (see figure below). After that it positions itself correctly (like the figure above) Does anyone know why this might be?
how adorner is positions http://img14.imageshack.us/img14/4766/fig2v.png
I have pasted the code for this below:
TextBoxAdorner.cs - this the adorner logic
public class TextBoxAdorner : Adorner
{
private TextBox _adornedElement;
private VisualCollection _visualChildren;
private Rectangle _shape;
private Canvas _container;
private Canvas _parentCanvas;
public TextBoxAdorner(UIElement adornedElement, Canvas parentCanvas)
: base(adornedElement)
{
_adornedElement = (TextBox)adornedElement;
_parentCanvas = parentCanvas;
_visualChildren = new VisualCollection(this);
_container = new Canvas();
_shape = new Rectangle();
_shape.Width = 100;
_shape.Height = 80;
_shape.Fill = Brushes.Blue;
_shape.Opacity = 0.5;
_container.Children.Add(_shape);
_visualChildren.Add(_container);
}
protected override Size ArrangeOverride(Size finalSize)
{
Point location = GetLocation();
_container.Arrange(new Rect(location, finalSize));
return finalSize;
}
private Point GetLocation()
{
if (_parentCanvas == null)
return new Point(0, 0);
Point translate;
double xloc = 0, yloc = _shape.Height - _adornedElement.ActualHeight;
if (yloc < 0) // textbox is bigger than the shape
yloc = 0;
else
{
translate = this.TranslatePoint(new Point(0, -yloc), _parentCanvas);
// coordinate is beyond the position of the parent canvas
if (translate.Y < 0) // this is true the first time it's run
yloc = 0;
else
yloc = -yloc;
}
translate = this.TranslatePoint(new Point(_shape.Width, 0), _parentCanvas);
// textbox is in right edge of the canvas
if (translate.X > _parentCanvas.ActualWidth)
{
double pos = translate.X - _parentCanvas.ActualWidth;
translate = this.TranslatePoint(new Point(-pos,0), _parentCanvas);
if (translate.X < 0)
xloc = 0;
else
xloc = translate.X;
}
return new Point(xloc, yloc);
}
protected override Size MeasureOverride(Size constraint)
{
Size myConstraint = new Size(_shape.Width, _shape.Height);
_container.Measure(myConstraint);
return _container.DesiredSize;
}
protected override Visual GetVisualChild(int index)
{
return _visualChildren[index];
}
protected override int VisualChildrenCount
{
get
{
return _visualChildren.Count;
}
}
}
The position of an Adorner is relative to the adorned element. If you want it to be to the top of your object, the value of yloc should be negative. However, the code you have also regards the boundaries of the Canvas. If there's not enough place for the rectangle above, it would put it below. Trying placing the TextBox a little lower in the Canvas.