I am working on implementing a feature into a UWP app where when a Grid item is hovered over in a AdaptiveGridView that after 2 seconds it will show a popup with more details about the hovered item in the form of another formatted Grid. This was working just fine until I needed to add a 2-second delay before the popup appears.
I have been adapting a similar thread in an attempt to make this work (How to use mousehover delay in c#?) but I have not been able to override the PointerEntered/PointerExited functions due to some errors:
Line 43: CS0505: 'HoverGrid.PointerEntered(PointerEventHandler)': cannot override because 'UIElement.PointerEntered' is not a function
Line 45: CS0079: The event 'UIElement.PointerEntered' can only appear on the left hand side of += or -=
To be honest, I am not sure if this is the best way to implement the hover delayed event either, but its something I am trying
Currently I have created a HoverGrid.cs class that is receiving the errors (below). I have tried tweaking the arguments/naming of the methods but it doesnt seem to do anything.
I have also tried to implement Timers directly in the page that has the events I am ultimately working with, but I was having issues with that which is why I am trying this method out if possible.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace ViperContentManager.Classes.Controls
{
public class HoverGrid : Windows.UI.Xaml.Controls.Grid
{
protected System.Timers.Timer timer;
public bool IsHoverEnabled { get; set; }
public double Delay { get; set; }
public event System.Timers.ElapsedEventHandler TimerElapsed
{
add
{
timer.Elapsed += value;
}
remove
{
timer.Elapsed -= value;
}
}
public HoverGrid()
{
// defaults: hover trigger enabled with 3000 ms delay
IsHoverEnabled = true;
Delay = 3000;
timer = new System.Timers.Timer
{
AutoReset = false,
Interval = Delay
};
}
protected override void PointerEntered(PointerEventHandler e)
{
base.PointerEntered();
if (IsHoverEnabled)
{
timer.Start();
}
}
protected override void PointerExited(PointerEventHandler e)
{
base.PointerExited();
timer.Stop();
}
}
}
For anyone curious, this is what the code-behind for the page that would be hosting the hoverGrid looks like (although HoverGrid is not yet implemented into my XAML at all). Forgive some of the extra variable declarations, I was trying to implement the hover function a few ways and haven't yet cleaned it up.
using Microsoft.Toolkit.Uwp.UI;
using Microsoft.Toolkit.Uwp.UI.Controls;
using System.Diagnostics;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Core;
namespace MyProject.Pages
{
public sealed partial class ImageUploaderPage : Page
{
public object selected = null;
public double thumbSize = 150;
//Variables for on-hover popup
public double detailPopLeft, detailPopRight, detailPopTop, detailPopBottom;
public Rect winBounds = new Rect(0,0,0,0);
public UIElement winContent = null;
public Windows.UI.Xaml.Window currentWindow = null;
public GeneralTransform hovTransform = null;
public Point hovPointToWindow = new Point(0,0);
public System.Timers.Timer hoverTimer = new System.Timers.Timer();
public object hoveredImage = null;
public PointerRoutedEventArgs hoveredImageArgs = null;
public ImageUploaderPage()
{
this.InitializeComponent();
//Create and set up the HoverTimer
hoverTimer = new System.Timers.Timer();
hoverTimer.Interval = 2000;
hoverTimer.Elapsed += OnTimerElapsed;
hoverTimer.AutoReset = true;
}
//public event System.Timers.ElapsedEventHandler TimerElapsed
//{ add { hoverTimer.Elapsed += value; } remove { hoverTimer.Elapsed -= value; } }
private void onImageHover(object sender, PointerRoutedEventArgs e)
{
hoveredImage = sender;
Grid img = (Grid)sender;
hoveredImageArgs = e;
hoverTimer.Stop();
hoverTimer.Start();
currentWindow = Window.Current;
winBounds = Window.Current.Bounds;
winContent = Window.Current.Content;
GeneralTransform transform = img.TransformToVisual(winContent);
hovPointToWindow = transform.TransformPoint(new Point(0, 0));
}
private void onImageEndHover(object sender, PointerRoutedEventArgs e)
{
hoverTimer.Stop();
hoveredImage = null;
hoveredImageArgs = null;
}
private void OnTimerElapsed(object source, System.Timers.ElapsedEventArgs e)
{
Debug.WriteLine("Timer elapsed!");
hoverTimer.Stop();
if (hoveredImage.GetType().ToString() == "Windows.UI.Xaml.Controls.Grid")
{
//Get the hovered image and associated arguments that were stored
Grid img = (Grid)hoveredImage;
PointerRoutedEventArgs f = hoveredImageArgs;
//Get image position and bounds
GeneralTransform transform = img.TransformToVisual(Window.Current.Content);
Point coordinatePointToWindow = transform.TransformPoint(new Point(0, 0));
Rect winBounds = Window.Current.Bounds;
img.Visibility = Visibility.Visible;
double imgX1 = coordinatePointToWindow.X;
double imgX2 = imgX1 + img.ActualWidth;
double imgY1 = coordinatePointToWindow.Y;
double imgY2 = imgY1 + img.ActualHeight;
// (other logic here to determine pop position and display things in the UI)
}
}
}
Your issue here is that you're trying to use a function to override a property.
Because UIElement.PointerEntered is an Event, you need to assign a delegate(of type PointerEventHandler) to it instead.
Here's an example snippet:
// Your constructor
public HoverGrid()
{
this.PointerEntered += HandlePointerEntered;
// Do other setup
}
protected void HandlePointerEntered(object sender, PointerRoutedEventArgs args)
{
// Handle somehow
}
I have a window on which I have some blur effects running. I want this window to be maximized so I set the Window State field to be Maximized in the designer.But the Window is not maximized and leaves some uncovered area in the top left corner. Ive tried multiple Start Position settings but none of them solve the problem.
The Settings
The Window
The code for the blurry Window
using System.Runtime.InteropServices;
namespace WF4
{
public partial class Form1 : Form
{
public Form1()
{
this.EnableBlur();
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.LimeGreen;
TransparencyKey = Color.LimeGreen;
InitializeComponent();
FormBorderStyle = FormBorderStyle.None;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
MessageBox.Show("Hllo");
}
}
}
public static class WindowExtension
{
[DllImport("user32.dll")]
static internal extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
public static void EnableBlur(this Form #this)
{
var accent = new AccentPolicy();
accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
var accentStructSize = Marshal.SizeOf(accent);
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var Data = new WindowCompositionAttributeData();
Data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
Data.SizeOfData = accentStructSize;
Data.Data = accentPtr;
SetWindowCompositionAttribute(#this.Handle, ref Data);
Marshal.FreeHGlobal(accentPtr);
}
}
enum AccentState
{
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
}
struct AccentPolicy
{
public AccentState AccentState;
public int AccentFlags;
public int GradientColor;
public int AnimationId;
}
struct WindowCompositionAttributeData
{
public WindowCompositionAttribute Attribute;
public IntPtr Data;
public int SizeOfData;
}
enum WindowCompositionAttribute
{
WCA_ACCENT_POLICY = 19
}
}
I tested Hans's suggestion and it worked well.
The solution is to set the FormBorderStyle property to None.
Make sure the background color is Control.
Calling EnableBlur() after InitializeComponent():
InitializeComponent();
this.EnableBlur();
Output:
I'm trying to create a custom scrollable panel as TableLayoutPanels scroll function is not very customisable.
I have a custom class that inherits from Microsoft.Visualbasics.Powerpacks.RectangleShape. This class is used to create the scroll bar object. It contains a MouseDrag Event that is supposed to be triggered when the mouse is pressed down on the scroll bar and will terminate when the mouse comes back up.
This ScrollBar object is instantiated in another custom class that inherits from Forms.Panel.
In the main form method the custom panel is instantiated and the MouseDrag event is added to the ScrollBar. When I click the ScrollBar nothing happens. I even tested with the built in Click event and again nothing happens. Any help would be much appreciated.
Scroll Bar Class:
class ScrollBar : RectangleShape
{
public event MouseEventHandler MouseDrag;
private bool mouseHeld = false;
public bool MouseHeld { get => mouseHeld; set => mouseHeld = value; }
public ScrollBar()
{
InitializeObject();
}
public ScrollBar(int x, int y, int width, int height) : base(x, y, width, height)
{
InitializeObject();
}
private void InitializeObject()
{
this.MouseDown += new MouseEventHandler(mouseClickEvent);
}
public void mouseClickEvent(object sender, MouseEventArgs e)
{
MouseHeld = true;
MouseDrag(this, null);
}
}
Custom Panel Class:
class CustomPanel : Panel
{
private ScrollBar verticalScrollBar;
public ScrollBar VerticalScrollBar { get => verticalScrollBar; set => verticalScrollBar = value; }
public CustomPanel()
{
PanelSetup();
}
public CustomPanel(Size _size)
{
this.Size = _size;
PanelSetup();
}
private void PanelSetup()
{
//Panel setup
this.BackColor = Color.White;
this.Location = new Point(125, 125);
this.BorderStyle = BorderStyle.FixedSingle;
//Behind scrollbar graphic
RectangleShape behindScrollGraphic = new RectangleShape();
behindScrollGraphic.Width = 21;
behindScrollGraphic.Height = this.Height;
behindScrollGraphic.Location = new Point(this.Width - behindScrollGraphic.Width, 0);
behindScrollGraphic.FillStyle = FillStyle.Solid;
behindScrollGraphic.FillColor = SystemColors.Control;
behindScrollGraphic.BorderColor = Color.Transparent;
//adding behind scroll bar to panel
ShapeContainer shapeContainer = new ShapeContainer();
shapeContainer.Shapes.Add(behindScrollGraphic);
this.Controls.Add(shapeContainer);
}
public virtual void AddVerticalScrollBar()
{
ShapeContainer rectangleShapeContainer = new ShapeContainer();
rectangleShapeContainer.Shapes.Add(VerticalScrollBar);
this.Controls.Add(rectangleShapeContainer);
}
public virtual void CreateScrollBar(int _barWidth, int _barHeight)
{
int barWidth = _barWidth;
int barHeight = _barHeight;
VerticalScrollBar = new ScrollBar(this.Width - barWidth - 7, 5, 12, 30);
VerticalScrollBar.FillStyle = FillStyle.Solid;
VerticalScrollBar.FillColor = SystemColors.ControlDark;
VerticalScrollBar.BorderColor = Color.Transparent;
}
}
Main Form Class:
public partial class Form1 : Form
{
private CustomPanel panel;
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
//Form setup
this.Size = new Size(500, 500);
this.BackColor = Color.White;
//Panel setup
panel = new CustomPanel(new Size(250, 250));
panel.CreateScrollBar(10, panel.Height - 2);
panel.AddVerticalScrollBar();
//Scroll Bar
panel.VerticalScrollBar.MouseDrag += new MouseEventHandler(mouseHeldMethod);
//Add panel to form
this.Controls.Add(panel);
}
private void mouseHeldMethod(object sender, MouseEventArgs e)
{
Console.WriteLine("test");
while (panel.VerticalScrollBar.MouseHeld)
{
Console.WriteLine("Held");
}
}
}
Figured the problem out before anyone wastes their time, the control was being obstructed by another control even though visibly the other control was behind it, nothing wrong with the event call.
I am trying to write something along of a "clean" code... I want to make a Pong game, for now based on Forms.
I want to divide the game nicely into classes.
I want to have a ball class, AI that inherits from player class, I want to use the premade Form class for setting main Form properties (width etc).
I made a player class as such and I would like to ask you if the approach for naming, getters and setters and the general idea is correct. Isn't certain bits (if not all of it) rather redundant or badly written, I do not want to base entire "project" on bad assumptions and multiply the same mistakes all over the code.
namespace Pong
{
public class Player
{
protected PictureBox PaddleBox { get; set; }
protected Size PlayerSize
{
get
{
return PlayerSize;
}
set
{
if (PlayerSize.Height > 0 && PlayerSize.Width > 0)
{
PlayerSize = new Size(value.Width, value.Height);
PaddleBox.Size = PlayerSize;
}
}
}
protected Point Location
{
get
{
return Location;
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
protected Color BackColor
{
get
{
return BackColor;
}
set
{
PaddleBox.BackColor = value;
}
}
public Player()
{
PaddleBox = new PictureBox();
}
}
}
FORM class looks something along of this for now, maybe I should pass parameters such as size,location and color in the constructor? What is the best?
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player();
player.PaddleBox.Size = new Size(20, 50);
player.PaddleBox.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2);
player.PaddleBox.BackColor = Color.Blue;
this.Controls.Add(player.PaddleBox);
gameTime = new Timer();
gameTime.Enabled = true;
}
void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
You got an issue here:
protected Point Location
{
get
{
return Location; // <--- this is a circular reference..
// meaning, it will recall this getter again.
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
use this instead:
protected Point Location
{
get
{
return PaddleBox.Location;
}
set
{
PaddleBox.Location = value;
}
}
Same with protected Color BackColor
Here is an example, how I would implement it (in your current programming style (powered by notepad))
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player(this);
player.PlayerSize = new Size(20, 50);
player.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2); // <-- the location is always the upperleft point. don't do this...
player.BackColor = Color.Blue;
gameTime = new Timer();
gameTime.Enabled = true;
}
private void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class Player
{
private PictureBox _paddleBox;
protected Size PlayerSize
{
get
{
return _paddleBox.Size;
}
set
{
if (PlayerSize.Height == 0 || PlayerSize.Width == 0)
throw new ArgumentException("Size must be greater than 0");
_paddleBox.Size = value;
}
}
protected Point Location
{
get { return PaddleBox.Location; }
set { PaddleBox.Location = value; }
}
protected Color BackColor
{
get { return PaddleBox.BackColor; }
set { PaddleBox.BackColor = value; }
}
public Player(Form form)
{
PaddleBox = new PictureBox();
form.Controls.Add(PaddleBox);
}
}
}
You should try to isolate the picturebox in your player class, this will separate the functionality of the form and the picturebox...
Basically when user resizes my application's window I want application to be same size when application is re-opened again.
At first I though of handling SizeChanged event and save Height and Width, but I think there must be easier solution.
Pretty simple problem, but I can not find easy solution to it.
Save the values in the user.config file.
You'll need to create the value in the settings file - it should be in the Properties folder. Create five values:
Top of type double
Left of type double
Height of type double
Width of type double
Maximized of type bool - to hold whether the window is maximized or not. If you want to store more information then a different type or structure will be needed.
Initialise the first two to 0 and the second two to the default size of your application, and the last one to false.
Create a Window_OnSourceInitialized event handler and add the following:
this.Top = Properties.Settings.Default.Top;
this.Left = Properties.Settings.Default.Left;
this.Height = Properties.Settings.Default.Height;
this.Width = Properties.Settings.Default.Width;
// Very quick and dirty - but it does the job
if (Properties.Settings.Default.Maximized)
{
WindowState = WindowState.Maximized;
}
NOTE: The set window placement needs to go in the on source initialised event of the window not the constructor, otherwise if you have the window maximised on a second monitor, it will always restart maximised on the primary monitor and you won't be able to access it.
Create a Window_Closing event handler and add the following:
if (WindowState == WindowState.Maximized)
{
// Use the RestoreBounds as the current values will be 0, 0 and the size of the screen
Properties.Settings.Default.Top = RestoreBounds.Top;
Properties.Settings.Default.Left = RestoreBounds.Left;
Properties.Settings.Default.Height = RestoreBounds.Height;
Properties.Settings.Default.Width = RestoreBounds.Width;
Properties.Settings.Default.Maximized = true;
}
else
{
Properties.Settings.Default.Top = this.Top;
Properties.Settings.Default.Left = this.Left;
Properties.Settings.Default.Height = this.Height;
Properties.Settings.Default.Width = this.Width;
Properties.Settings.Default.Maximized = false;
}
Properties.Settings.Default.Save();
This will fail if the user makes the display area smaller - either by disconnecting a screen or changing the screen resolution - while the application is closed so you should add a check that the desired location and size is still valid before applying the values.
Actually you don't need to use code-behind to do that (except for saving the settings). You can use a custom markup extension to bind the window size and position to the settings like this :
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfApplication1"
Title="Window1"
Height="{my:SettingBinding Height}"
Width="{my:SettingBinding Width}"
Left="{my:SettingBinding Left}"
Top="{my:SettingBinding Top}">
You can find the code for this markup extension here :
http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/
While you can "roll your own" and manually save the settings somewhere, and in general it will work, it is very easy to not handle all of the cases correctly. It is much better to let the OS do the work for you, by calling GetWindowPlacement() at exit and SetWindowPlacement() at startup. It handles all of the crazy edge cases that can occur (multiple monitors, save the normal size of the window if it is closed while maximized, etc.) so that you don't have to.
This MSDN Sample shows how to use these with a WPF app. The sample isn't perfect (the window will start in the upper left corner as small as possible on first run, and there is some odd behavior with the Settings designer saving a value of type WINDOWPLACEMENT), but it should at least get you started.
The "long form" binding that Thomas posted above requires almost no coding, just make sure you have the namespace binding:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:p="clr-namespace:WpfApplication1.Properties"
Title="Window1"
Height="{Binding Source={x:Static p:Settings.Default}, Path=Height, Mode=TwoWay}"
Width="{Binding Source={x:Static p:Settings.Default}, Path=Width, Mode=TwoWay}"
Left="{Binding Source={x:Static p:Settings.Default}, Path=Left, Mode=TwoWay}"
Top="{Binding Source={x:Static p:Settings.Default}, Path=Top, Mode=TwoWay}">
Then to save on the code-behind:
private void frmMain_Closed(object sender, EventArgs e)
{
Properties.Settings.Default.Save();
}
Alternatively, you might like the following approach too (see source). Add the WindowSettings class to your project and insert WindowSettings.Save="True" in your main window's header:
<Window x:Class="YOURPROJECT.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Services="clr-namespace:YOURNAMESPACE.Services"
Services:WindowSettings.Save="True">
Where WindowSettings is defined as follows:
using System;
using System.ComponentModel;
using System.Configuration;
using System.Windows;
namespace YOURNAMESPACE.Services
{
/// <summary>
/// Persists a Window's Size, Location and WindowState to UserScopeSettings
/// </summary>
public class WindowSettings
{
#region Fields
/// <summary>
/// Register the "Save" attached property and the "OnSaveInvalidated" callback
/// </summary>
public static readonly DependencyProperty SaveProperty = DependencyProperty.RegisterAttached("Save", typeof (bool), typeof (WindowSettings), new FrameworkPropertyMetadata(OnSaveInvalidated));
private readonly Window mWindow;
private WindowApplicationSettings mWindowApplicationSettings;
#endregion Fields
#region Constructors
public WindowSettings(Window pWindow) { mWindow = pWindow; }
#endregion Constructors
#region Properties
[Browsable(false)] public WindowApplicationSettings Settings {
get {
if (mWindowApplicationSettings == null) mWindowApplicationSettings = CreateWindowApplicationSettingsInstance();
return mWindowApplicationSettings;
}
}
#endregion Properties
#region Methods
public static void SetSave(DependencyObject pDependencyObject, bool pEnabled) { pDependencyObject.SetValue(SaveProperty, pEnabled); }
protected virtual WindowApplicationSettings CreateWindowApplicationSettingsInstance() { return new WindowApplicationSettings(this); }
/// <summary>
/// Load the Window Size Location and State from the settings object
/// </summary>
protected virtual void LoadWindowState() {
Settings.Reload();
if (Settings.Location != Rect.Empty) {
mWindow.Left = Settings.Location.Left;
mWindow.Top = Settings.Location.Top;
mWindow.Width = Settings.Location.Width;
mWindow.Height = Settings.Location.Height;
}
if (Settings.WindowState != WindowState.Maximized) mWindow.WindowState = Settings.WindowState;
}
/// <summary>
/// Save the Window Size, Location and State to the settings object
/// </summary>
protected virtual void SaveWindowState() {
Settings.WindowState = mWindow.WindowState;
Settings.Location = mWindow.RestoreBounds;
Settings.Save();
}
/// <summary>
/// Called when Save is changed on an object.
/// </summary>
private static void OnSaveInvalidated(DependencyObject pDependencyObject, DependencyPropertyChangedEventArgs pDependencyPropertyChangedEventArgs) {
var window = pDependencyObject as Window;
if (window != null)
if ((bool) pDependencyPropertyChangedEventArgs.NewValue) {
var settings = new WindowSettings(window);
settings.Attach();
}
}
private void Attach() {
if (mWindow != null) {
mWindow.Closing += WindowClosing;
mWindow.Initialized += WindowInitialized;
mWindow.Loaded += WindowLoaded;
}
}
private void WindowClosing(object pSender, CancelEventArgs pCancelEventArgs) { SaveWindowState(); }
private void WindowInitialized(object pSender, EventArgs pEventArgs) { LoadWindowState(); }
private void WindowLoaded(object pSender, RoutedEventArgs pRoutedEventArgs) { if (Settings.WindowState == WindowState.Maximized) mWindow.WindowState = Settings.WindowState; }
#endregion Methods
#region Nested Types
public class WindowApplicationSettings : ApplicationSettingsBase
{
#region Constructors
public WindowApplicationSettings(WindowSettings pWindowSettings) { }
#endregion Constructors
#region Properties
[UserScopedSetting] public Rect Location {
get {
if (this["Location"] != null) return ((Rect) this["Location"]);
return Rect.Empty;
}
set { this["Location"] = value; }
}
[UserScopedSetting] public WindowState WindowState {
get {
if (this["WindowState"] != null) return (WindowState) this["WindowState"];
return WindowState.Normal;
}
set { this["WindowState"] = value; }
}
#endregion Properties
}
#endregion Nested Types
}
}
There's a NuGet Project RestoreWindowPlace
see on github that does all this for you, saving the information in an XML file.
To get it to work on a window, it's as simple as calling:
((App)Application.Current).WindowPlace.Register(this);
In App you create the class that manages your windows. See the github link above for more information.
I made a more generic solution based on RandomEngys brilliant answer. It saves the position to file in the running folder and you don't need to create new properties for each new window you create. This sollution works great for me with minimal code in code behind.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Interop;
using System.Xml;
using System.Xml.Serialization;
namespace WindowPlacementNameSpace
{
// RECT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
// POINT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
// WINDOWPLACEMENT stores the position, size, and state of a window
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT minPosition;
public POINT maxPosition;
public RECT normalPosition;
}
public static class WindowPlacement
{
private static readonly Encoding Encoding = new UTF8Encoding();
private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(WINDOWPLACEMENT));
[DllImport("user32.dll")]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private static void SetPlacement(IntPtr windowHandle, string placementXml)
{
if (string.IsNullOrEmpty(placementXml))
{
return;
}
byte[] xmlBytes = Encoding.GetBytes(placementXml);
try
{
WINDOWPLACEMENT placement;
using (MemoryStream memoryStream = new MemoryStream(xmlBytes))
{
placement = (WINDOWPLACEMENT)Serializer.Deserialize(memoryStream);
}
placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
placement.flags = 0;
placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd);
SetWindowPlacement(windowHandle, ref placement);
}
catch (InvalidOperationException)
{
// Parsing placement XML failed. Fail silently.
}
}
private static string GetPlacement(IntPtr windowHandle)
{
WINDOWPLACEMENT placement;
GetWindowPlacement(windowHandle, out placement);
using (MemoryStream memoryStream = new MemoryStream())
{
using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8))
{
Serializer.Serialize(xmlTextWriter, placement);
byte[] xmlBytes = memoryStream.ToArray();
return Encoding.GetString(xmlBytes);
}
}
}
public static void ApplyPlacement(this Window window)
{
var className = window.GetType().Name;
try
{
var pos = File.ReadAllText(Directory + "\\" + className + ".pos");
SetPlacement(new WindowInteropHelper(window).Handle, pos);
}
catch (Exception exception)
{
Log.Error("Couldn't read position for " + className, exception);
}
}
public static void SavePlacement(this Window window)
{
var className = window.GetType().Name;
var pos = GetPlacement(new WindowInteropHelper(window).Handle);
try
{
File.WriteAllText(Directory + "\\" + className + ".pos", pos);
}
catch (Exception exception)
{
Log.Error("Couldn't write position for " + className, exception);
}
}
private static string Directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
}
In your code behind you add these two methods
///This method is save the actual position of the window to file "WindowName.pos"
private void ClosingTrigger(object sender, EventArgs e)
{
this.SavePlacement();
}
///This method is load the actual position of the window from the file
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
this.ApplyPlacement();
}
in the xaml window you add this
Closing="ClosingTrigger"
The default way of solving it is to use settings files. The problem with settings files is that you have to define all the settings and write the code that copies data back and forth yourself. Quite tedious if you have a lot of properties to keep track of.
I made a pretty flexible and very easy to use library for this, you just tell it which properties of which object to track and it does the rest. You can configure the crap out of it too if you like.
The library is called Jot (github), here is an old CodeProject article I wrote about it.
Here's how you'd use it to keep track of a window's size and location:
public MainWindow()
{
InitializeComponent();
_stateTracker.Configure(this)
.IdentifyAs("MyMainWindow")
.AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
.RegisterPersistTrigger(nameof(Closed))
.Apply();
}
Jot vs. settings files: With Jot there's considerably less code, and it's a lot less error prone since you only need to mention each property once.
With settings files you need to mention each property 5 times: once when you explicitly create the property and an additional four times in the code that copies the values back and forth.
Storage, serialization etc are completely configurable. Also, when using IOC, you can even hook it up so that it applies tracking automatically to all objects it resolves so that all you need to do to make a property persistent is slap a [Trackable] attribute on it.
I'm writing all this because I think the library is top notch and I want to mouth off about it.
I wrote a quick class which does this. Here is how it's called:
public MainWindow()
{
FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings,
s =>
{
Settings.Default.MainWindowSettings = s;
Settings.Default.Save();
});
InitializeComponent();
...
And here is the code:
public class FormSizeSaver
{
private readonly Window window;
private readonly Func<FormSizeSaverSettings> getSetting;
private readonly Action<FormSizeSaverSettings> saveSetting;
private FormSizeSaver(Window window, Func<string> getSetting, Action<string> saveSetting)
{
this.window = window;
this.getSetting = () => FormSizeSaverSettings.FromString(getSetting());
this.saveSetting = s => saveSetting(s.ToString());
window.Initialized += InitializedHandler;
window.StateChanged += StateChangedHandler;
window.SizeChanged += SizeChangedHandler;
window.LocationChanged += LocationChangedHandler;
}
public static FormSizeSaver RegisterForm(Window window, Func<string> getSetting, Action<string> saveSetting)
{
return new FormSizeSaver(window, getSetting, saveSetting);
}
private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
{
var s = getSetting();
s.Height = e.NewSize.Height;
s.Width = e.NewSize.Width;
saveSetting(s);
}
private void StateChangedHandler(object sender, EventArgs e)
{
var s = getSetting();
if (window.WindowState == WindowState.Maximized)
{
if (!s.Maximized)
{
s.Maximized = true;
saveSetting(s);
}
}
else if (window.WindowState == WindowState.Normal)
{
if (s.Maximized)
{
s.Maximized = false;
saveSetting(s);
}
}
}
private void InitializedHandler(object sender, EventArgs e)
{
var s = getSetting();
window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal;
if (s.Height != 0 && s.Width != 0)
{
window.Height = s.Height;
window.Width = s.Width;
window.WindowStartupLocation = WindowStartupLocation.Manual;
window.Left = s.XLoc;
window.Top = s.YLoc;
}
}
private void LocationChangedHandler(object sender, EventArgs e)
{
var s = getSetting();
s.XLoc = window.Left;
s.YLoc = window.Top;
saveSetting(s);
}
}
[Serializable]
internal class FormSizeSaverSettings
{
public double Height, Width, YLoc, XLoc;
public bool Maximized;
public override string ToString()
{
using (var ms = new MemoryStream())
{
var bf = new BinaryFormatter();
bf.Serialize(ms, this);
ms.Position = 0;
byte[] buffer = new byte[(int)ms.Length];
ms.Read(buffer, 0, buffer.Length);
return Convert.ToBase64String(buffer);
}
}
internal static FormSizeSaverSettings FromString(string value)
{
try
{
using (var ms = new MemoryStream(Convert.FromBase64String(value)))
{
var bf = new BinaryFormatter();
return (FormSizeSaverSettings) bf.Deserialize(ms);
}
}
catch (Exception)
{
return new FormSizeSaverSettings();
}
}
}
Create a string named WindowXml in your default Settings.
Use this extension method on your Window Loaded and Closing events to restore and save Window size and location.
using YourProject.Properties;
using System;
using System.Linq;
using System.Windows;
using System.Xml.Linq;
namespace YourProject.Extensions
{
public static class WindowExtensions
{
public static void SaveSizeAndLocation(this Window w)
{
try
{
var s = "<W>";
s += GetNode("Top", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Top : w.Top);
s += GetNode("Left", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Left : w.Left);
s += GetNode("Height", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Height : w.Height);
s += GetNode("Width", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Width : w.Width);
s += GetNode("WindowState", w.WindowState);
s += "</W>";
Settings.Default.WindowXml = s;
Settings.Default.Save();
}
catch (Exception)
{
}
}
public static void RestoreSizeAndLocation(this Window w)
{
try
{
var xd = XDocument.Parse(Settings.Default.WindowXml);
w.WindowState = (WindowState)Enum.Parse(typeof(WindowState), xd.Descendants("WindowState").FirstOrDefault().Value);
w.Top = Convert.ToDouble(xd.Descendants("Top").FirstOrDefault().Value);
w.Left = Convert.ToDouble(xd.Descendants("Left").FirstOrDefault().Value);
w.Height = Convert.ToDouble(xd.Descendants("Height").FirstOrDefault().Value);
w.Width = Convert.ToDouble(xd.Descendants("Width").FirstOrDefault().Value);
}
catch (Exception)
{
}
}
private static string GetNode(string name, object value)
{
return string.Format("<{0}>{1}</{0}>", name, value);
}
}
}
You might like this:
public class WindowStateHelper
{
public static string ToXml(System.Windows.Window win)
{
XElement bounds = new XElement("Bounds");
if (win.WindowState == System.Windows.WindowState.Maximized)
{
bounds.Add(new XElement("Top", win.RestoreBounds.Top));
bounds.Add(new XElement("Left", win.RestoreBounds.Left));
bounds.Add(new XElement("Height", win.RestoreBounds.Height));
bounds.Add(new XElement("Width", win.RestoreBounds.Width));
}
else
{
bounds.Add(new XElement("Top", win.Top));
bounds.Add(new XElement("Left", win.Left));
bounds.Add(new XElement("Height", win.Height));
bounds.Add(new XElement("Width", win.Width));
}
XElement root = new XElement("WindowState",
new XElement("State", win.WindowState.ToString()),
new XElement("Visibility", win.Visibility.ToString()),
bounds);
return root.ToString();
}
public static void FromXml(string xml, System.Windows.Window win)
{
try
{
XElement root = XElement.Parse(xml);
string state = root.Descendants("State").FirstOrDefault().Value;
win.WindowState = (System.Windows.WindowState)Enum.Parse(typeof(System.Windows.WindowState), state);
state = root.Descendants("Visibility").FirstOrDefault().Value;
win.Visibility = (System.Windows.Visibility)Enum.Parse(typeof(System.Windows.Visibility), state);
XElement bounds = root.Descendants("Bounds").FirstOrDefault();
win.Top = Convert.ToDouble(bounds.Element("Top").Value);
win.Left = Convert.ToDouble(bounds.Element("Left").Value);
win.Height = Convert.ToDouble(bounds.Element("Height").Value);
win.Width = Convert.ToDouble(bounds.Element("Width").Value);
}
catch (Exception x)
{
System.Console.WriteLine(x.ToString());
}
}
}
When the app closes:
Properties.Settings.Default.Win1Placement = WindowStateHelper.ToXml(win1);
Properties.Settings.Default.Win2Placement = WindowStateHelper.ToXml(win2);
...
When the app starts:
WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
...
I'm using the answer from Lance Cleveland and bind the Setting.
But i'm using some more Code to avoid that my Window is out of Screen.
private void SetWindowSettingsIntoScreenArea()
{
// first detect Screen, where we will display the Window
// second correct bottom and right position
// then the top and left position.
// If Size is bigger than current Screen, it's still possible to move and size the Window
// get the screen to display the window
var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point((int)Default.Left, (int)Default.Top));
// is bottom position out of screen for more than 1/3 Height of Window?
if (Default.Top + (Default.Height / 3) > screen.WorkingArea.Height)
Default.Top = screen.WorkingArea.Height - Default.Height;
// is right position out of screen for more than 1/2 Width of Window?
if (Default.Left + (Default.Width / 2) > screen.WorkingArea.Width)
Default.Left = screen.WorkingArea.Width - Default.Width;
// is top position out of screen?
if (Default.Top < screen.WorkingArea.Top)
Default.Top = screen.WorkingArea.Top;
// is left position out of screen?
if (Default.Left < screen.WorkingArea.Left)
Default.Left = screen.WorkingArea.Left;
}