Related
I have created a Windows Form App for our works Windows Tablets (windows 10) to track location.
The class I have created is below:
class LocationServices
{
private GeoCoordinateWatcher myWatcher;
private bool fgWatcherStarted = false;
public LocationServices()
{
myWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
fgWatcherStarted = myWatcher.TryStart(true, System.TimeSpan.FromMilliseconds(1000));
}
public LatLon GetDeviceLocation()
{
LatLon myReturn = new LatLon();
System.Device.Location.GeoCoordinate myPosition = new System.Device.Location.GeoCoordinate();
try
{
if (!fgWatcherStarted)
{
fgWatcherStarted = myWatcher.TryStart(true, System.TimeSpan.FromMilliseconds(1000));
}
myPosition = myWatcher.Position.Location;
if (myPosition.IsUnknown)
{
myReturn.Latitude = 0;
myReturn.Longitude = 0;
myReturn.strMessage = "Unknown Position";
}
else
{
myReturn.Latitude = myPosition.Latitude;
myReturn.Longitude = myPosition.Longitude;
myReturn.strMessage = myPosition.Course.ToString();
}
}
catch (Exception ex)
{
myReturn.Latitude = 0;
myReturn.Longitude = 0;
myReturn.strMessage = ex.Message.ToString();
}
return myReturn;
}
}
In my code I am running through every few seconds and getting the location by calling the class above.
If I am connected to wifi (running from my desk) it gets the location instantly and works as expected, but from the device it returns 0 for a significant amount of time and then suddenly starts working and works perfectly fine with no issues.
Is there anything I can do to make this start quicker? I initially thought it could be the location/signal but I have tried loading in the same place and leaving it and once it initialises it works perfectly but it's the initial coordinates that take ages to load.
Looks as though there needs to be a position change for it to return a value, see the answer here: https://stackoverflow.com/a/52910209/6639187
Not quite sure about your way of getting the coordinates, I used to do the following method - hope this helps (run it in a console app and check as follows)
class Program
{
private static GeoCoordinateWatcher Watcher;
static void Main(string[] args)
{
Watcher = new GeoCoordinateWatcher();
Watcher.StatusChanged += Watcher_StatusChanged;
Watcher.Start();
Console.ReadLine();
}
static void Watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
if (e.Status == GeoPositionStatus.Ready)
{
if (Watcher.Position.Location.IsUnknown)
{
Console.Write("Cannot find location");
}
else
{
Console.WriteLine("Lat: " + Watcher.Position.Location.Latitude.ToString());
Console.WriteLine("Lon: " + Watcher.Position.Location.Longitude.ToString());
}
}
}
}
I know the terminology of this question must be all wrong, but please bear with me and try to see things from my layman's point of view (I have no formation in computer technology, I'm a self taught enthusiast. The closest I get from a formal education in programming language is my school's robotics club).
What I want is to be able to use managed DirectX 12 as the "background" of my application, with a game loop and all. And, if possible, to be able to have WPF controls like a ribbon or a toolbox or a menu around the actual directX game. I've been looking all over the internet and all I find is very old stuff for Windows and DirectX 9.0; i'm hoping there's something new these days.
I tryed the Windows Form approach, which is basically this:
using System;
using System.Windows;
using System.Windows.Interop;
using Microsoft.DirectX.Direct3D;
using DColor = System.Drawing.Color;
public partial class MainWindow : Window
{
Device device;
public MainWindow()
{
InitializeComponent();
initDevice();
}
private void initDevice()
{
try
{
PresentParameters parameters = new PresentParameters();
parameters.Windowed = true;
parameters.SwapEffect = SwapEffect.Discard;
IntPtr windowHandle = new WindowInteropHelper(this).Handle;
device = new Device(0, DeviceType.Hardware, windowHandle, CreateFlags.HardwareVertexProcessing, parameters);
}
catch(Exception e)
{
MessageBox.Show("initDevice threw an Exception\n" + e.Message, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void render()
{
device.Clear(ClearFlags.Target, DColor.LightGreen, 0f, 1);
device.Present();
}
}
No exception is thrown, the window is never rendered at all. The application runs, but the window doesn't show up. I didn't think this would work, because there's no game loop and render doesn't get invoked from anywhere, but I didn't expect the window not even being displayed. If I comment out the line that invokes initDevice(), WPF's blank window is shown normally
Then I that discovered the CompositionTarget.Rendering event gets called once every frame (or tick?), so the handler for this event must be used as the game loop.
and so I tried this:
using System;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Forms.Integration;
using Microsoft.DirectX.Direct3D;
using DColor = System.Drawing.Color;
using System.Windows.Forms;
public partial class MainWindow : Window
{
Device device = null;
MemoryStream stream;
PictureBox display;
WindowsFormsHost host;
public MainWindow()
{
InitializeComponent();
initDevice();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
render();
}
private void initDevice()
{
try
{
PresentParameters parameters = new PresentParameters();
parameters.Windowed = true;
parameters.SwapEffect = SwapEffect.Discard;
device = new Device(0, DeviceType.Hardware, display, CreateFlags.HardwareVertexProcessing, parameters);
stream = new MemoryStream();
device.SetRenderTarget(0, new Surface(device, stream, Pool.Managed));
}
catch(Exception e)
{
System.Windows.MessageBox.Show("initDevice threw an Exception\n" + e.Message, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void render()
{
device.Clear(ClearFlags.Target, DColor.LightGreen, 0f, 1);
device.Present();
display.Image = Image.FromStream(stream);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
host = new WindowsFormsHost();
display = new PictureBox();
host.Child = display;
mainGrid.Children.Add(host);
}
}
Still no window is shown, even though the application is running and not crashing.
Finally I tried the same thing but without handling CompositionTarget.Rendering, but using a DispatcherTimer instead, and called render from inside its Tick event handler. Same result: no Window.
Can anyone point me to the right direction?
I know it's an old post but for those who search a solution, there is the one I found.
The solution is based on D3D11Image from the project mentioned by Chuck.
1. On Window_Loaded_Event :
private void Window_Loaded(object sender, RoutedEventArgs e) {
InitDx12();
CreateDx11Stuff();
DxImage.SetPixelSize(1280, 720);
DxImage.WindowOwner = (new System.Windows.Interop.WindowInteropHelper(this)).Handle;
DxImage.OnRender += Render;
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
2. Create Dx11 Stuff :
private void CreateDx11Stuff() {
D3D11Device = SharpDX.Direct3D11.Device.CreateFromDirect3D12(D3D12Device, SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.Debug, new[] { SharpDX.Direct3D.FeatureLevel.Level_12_1 }, Adatper, CommandQueue);
D3D11On12 = ComObject.QueryInterfaceOrNull<SharpDX.Direct3D11.Device11On12>(D3D11Device.NativePointer);
for(int idx = 0; idx < BackBufferCount; idx++) {
D3D11On12.CreateWrappedResource(BackBuffers[idx], new D3D11ResourceFlags { BindFlags = (int)BindFlags.RenderTarget, CPUAccessFlags = 0, MiscFlags = (int)0x2L, StructureByteStride = 0 }, (int)ResourceStates.RenderTarget, (int)ResourceStates.Present, typeof(Texture2D).GUID, out D3D11BackBuffers[idx]);
}
}
3. CompositionTarget Rendering : is quite simple
private void CompositionTarget_Rendering(object sender, EventArgs e) {
DxImage.RequestRender();
}
4. The render function :
private void Render(IntPtr surface, bool newSurface) {
DoDx12Rendering();
var unk = new ComObject(surface);
var dxgiRes = unk.QueryInterface<SharpDX.DXGI.Resource>();
var tempRes = D3D11Device.OpenSharedResource<SharpDX.Direct3D11.Resource>(dxgiRes.SharedHandle);
var backBuffer = tempRes.QueryInterface<Texture2D>();
var d3d11BackBuffer = D3D11BackBuffers[CurrentFrame];
D3D11On12.AcquireWrappedResources(new[] { d3d11BackBuffer }, 1);
D3D11Device.ImmediateContext.CopyResource(d3d11BackBuffer, backBuffer);
D3D11Device.ImmediateContext.Flush();
D3D11On12.ReleaseWrappedResources(new[] { d3d11BackBuffer }, 1);
}
Bonus
You can also do you rendering without the composition target event.
For this, in the Render callback --> void Render(IntPtr surface, bool newSurface), just store the handle of the surface.
Call DxImage.RequestRender() for this.
Do you render in your render loop and add the D3D11on12 to D3D11 copy at the end.
Note
If you handle the resize event, think to resize the DxImage with DxImage.SetPixelSize then recreate your wrapped resources.
More Explanations
I create the Device like this :
_D3D9Device = new DeviceEx(new Direct3DEx(), 0, DeviceType.Hardware, handle, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, new SharpDX.Direct3D9.PresentParameters(1, 1) {
Windowed = true,
SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
DeviceWindowHandle = handle,
PresentationInterval = PresentInterval.Immediate
});
_D3D11Device = SharpDX.Direct3D11.Device.CreateFromDirect3D12(Device, DeviceCreationFlags.BgraSupport, new[] { SharpDX.Direct3D.FeatureLevel.Level_12_0 }, null, RenderCommandQueue);
And I create the Dx11 and Dx9 FBOs like that :
private void CreateWPFInteropFBO()
{
var desc = new Texture2DDescription {
ArraySize = 1,
BindFlags = BindFlags.RenderTarget,
Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
Height = RenderTargetSize.Height,
Width = RenderTargetSize.Width,
MipLevels = 1,
OptionFlags = ResourceOptionFlags.Shared,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
Usage = ResourceUsage.Default
};
Dx11Texture?.Dispose();
Dx11Texture = new Texture2D(_D3D11Device, desc);
var ptr = Dx11Texture.NativePointer;
var comobj = new ComObject(ptr);
using (var dxgiRes = comobj.QueryInterface<SharpDX.DXGI.Resource>()) {
var sharedHandle = dxgiRes.SharedHandle;
var texture = new Texture(_D3D9Device, desc.Width, desc.Height, 1, SharpDX.Direct3D9.Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref sharedHandle);
Dx9Surface?.Dispose();
Dx9Surface = texture.GetSurfaceLevel(0);
}
}
In fact they are the sames.
Then, after rendering I copy my Dx12 RenderTarget to my Dx11 RenderTarget.
var ptr = GetDx12ResourceFromHandle(Resources.Dx11Texture.NativePointer);
commandList.CopyResource(ptr, Resources.RenderTarget);
In my RenderLoop I update the BackBuffer like this :
private async void UpdateDx9Image()
{
if (Application.Current == null) return;
await Application.Current?.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
if (DxImage.TryLock(new Duration(new TimeSpan(0, 0, 0, 0, 16))))
{
DxImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _Renderer.Resources.Dx9Surface.NativePointer, false);
DxImage.AddDirtyRect(new Int32Rect(0, 0, _Renderer.Resources.Dx9Surface.Description.Width, _Renderer.Resources.Dx9Surface.Description.Height));
}
DxImage.Unlock();
}));
}
This project should help. It currently only supports Direct3D 11, but the principles are the same with DirectX 12.
That said, why do you need DirectX 12 instead of just sticking with DirectX 11? The answer should be something more technical than "12 is bigger than 11."
I'm building a series of apps for my place of business so I'm trying to create my own printing class that I can refer to for all of my applications.
The issue is, I'm trying to figure out a way for the application to tell the class when to print the page, and I am unable to find a way to do so.
For example, this is what I have so far:
// Report Variables
private bool bPrinting = false;
private int iPage = 0;
private float fOverflow = 0.00F;
private string sPrintLine = null;
private Font fontTmpFont = null;
private PrintPageEventArgs ppeaEv = null;
private Margins mMargins = new System.Drawing.Printing.Margins(25, 25, 25, 25); // Set wide margins
// Clear the print line
public void LineClear()
{
sPrintLine = null;
}
// Insert a string into the print line at the specified position within the line
public void LineInsert(string _InsertString, int _InsertPosition)
{
if (sPrintLine.Length <= _InsertPosition)
sPrintLine = sPrintLine.PadLeft(_InsertPosition) + _InsertString;
else if (sPrintLine.Length <= (_InsertPosition + _InsertString.Length))
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString;
else
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString + sPrintLine.Substring(_InsertPosition + _InsertString.Length);
}
// Check to see if the line we're trying to print is at the end of the page
public bool AtEndOfPage()
{
return AtEndOfPage(new Font("Courier", 10));
}
public bool AtEndOfPage(Font _Font)
{
if ((fOverflow + _Font.GetHeight(ppeaEv.Graphics)) > ppeaEv.MarginBounds.Height)
return true;
else
return false;
}
// Attempt to print the line
public void LinePrint()
{
LinePrint(null, null);
}
public void LinePrint(Font _Font)
{
LinePrint(_Font, null);
}
public void LinePrint(Font _Font, Brush _Color)
{
if (_Font == null)
_Font = new Font("Courier", 10);
if (_Color == null)
_Color = Brushes.Black;
ppeaEv.Graphics.DrawString(sPrintLine, _Font, _Color,
ppeaEv.MarginBounds.Left, ppeaEv.MarginBounds.Top + fOverflow,
new StringFormat()); // 'Draw' line on page
fOverflow += _Font.GetHeight(ppeaEv.Graphics);
}
// We are done with the report, tell the Print Service to finish up
public void EndReport()
{
ppeaEv.HasMorePages = false;
}
// This is what gets called when the user clicks on 'Print'
private void Print_Click(object sender, EventArgs e)
{
PrintDocument printDocument = new PrintDocument();
printDocument.DefaultPageSettings.Margins = mMargins; // Set margins for pages
printDocument.DefaultPageSettings.Landscape = false; // Set Portrait mode
printDocument.BeginPrint += new PrintEventHandler(printDocument_BeginPrint);
printDocument.EndPrint += new PrintEventHandler(printDocument_EndPrint);
PrintDialog printDialog = new PrintDialog();
printDialog.Document = printDocument; // Set the Document for this print dialog
printDialog.AllowSomePages = true; // Allow the user to select only some pages to print
printDialog.ShowHelp = true; // Allow help button
DialogResult result = printDialog.ShowDialog();
if (result == DialogResult.OK)
{
printDocument.Print(); // Raises PrintPage event
}
printDocument.Dispose();
printDialog.Dispose();
}
void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
iPage = 0;
fOverflow = 0.00F;
}
void printDocument_EndPrint(object sender, PrintEventArgs e)
{
}
As you may be able to tell, I'm trying to fill the page externally to the class, and then have the app tell the class when to print the page when "AtEndOfPage"
I know about "printDocument.PrintPage", but that doesn't seem to be what I need; It builds the page internal to the method and then prints it. I'm not going to be building the print page within that method.
I also am trying to allow for multiple fonts on a single page.
Is there a way to do this?
Thank you all in advance,
Robert
hello creating a custom object may be a widely published topic, but my lack of coding skills proves problematic in actually implementing what i'm trying to do.
in a nutshell i'm adding controls at runtime in a flowpanelLayout. right now it's just listboxes, that code is all working fine. i would like a way to label the listboxes that are getting added, i can't think of a better way to do this than to use a text label. i was thinking it would be slick to create some sort of custom control (if possible) which is a listbox and a textlabel like one above the other or something. this way i can add the new custom control in my current code and assign the listbox attributes and label text, etc all in one motion.
this is what i was thinking, maybe there's even a better way to do this.
my current listview creation code:
public void addListView()
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
newListView.DragDrop += listView_DragDrop;
newListView.DragEnter += listView_DragEnter;
newListView.MouseDoubleClick += listView_MouseDoubleClick;
newListView.MouseDown += listView_MouseDown;
newListView.DragOver += listView_DragOver;
newListView.Width = 200;
newListView.Height = 200;
newListView.View = View.Tile;
newListView.MultiSelect = false;
flowPanel.Controls.Add(newListView);
numWO++;
numberofWOLabel.Text = numWO.ToString();
}
maybe the actual best answer is simply to also add a textlabel here and define some set coordinates to put it. let me know what you think.
if a custom control is the way to go, please provide some resource or example for me - i'd appreciate it.
Here is a custom user control that can do that:
You just need to set TitleLabelText to set the title.
[Category("Custom User Controls")]
public class ListBoxWithTitle : ListBox
{
private Label titleLabel;
public ListBoxWithTitle()
{
this.SizeChanged +=new EventHandler(SizeSet);
this.LocationChanged +=new EventHandler(LocationSet);
this.ParentChanged += new EventHandler(ParentSet);
}
public string TitleLabelText
{
get;
set;
}
//Ensures the Size, Location and Parent have been set before adding text
bool isSizeSet = false;
bool isLocationSet = false;
bool isParentSet = false;
private void SizeSet(object sender, EventArgs e)
{
isSizeSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void LocationSet(object sender, EventArgs e)
{
isLocationSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void ParentSet(object sender, EventArgs e)
{
isParentSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void PositionLabel()
{
//Initializes text label
titleLabel = new Label();
//Positions the text 10 pixels below the Listbox.
titleLabel.Location = new Point(this.Location.X, this.Location.Y + this.Size.Height + 10);
titleLabel.AutoSize = true;
titleLabel.Text = TitleLabelText;
this.Parent.Controls.Add(titleLabel);
}
}
Example use:
public Form1()
{
InitializeComponent();
ListBoxWithTitle newitem = new ListBoxWithTitle();
newitem.Size = new Size(200, 200);
newitem.Location = new Point(20, 20);
newitem.TitleLabelText = "Test";
this.Controls.Add(newitem);
}
I have a Windows Forms application with a normal window. Now when I close the application and restart it, I want that the main window appears at the same location on my screen with the same size of the moment when it was closed.
Is there an easy way in Windows Forms to remember the screen location and window size (and if possible the window state) or does everything have to be done by hand?
If you add this code to your FormClosing event handler:
if (WindowState == FormWindowState.Maximized)
{
Properties.Settings.Default.Location = RestoreBounds.Location;
Properties.Settings.Default.Size = RestoreBounds.Size;
Properties.Settings.Default.Maximised = true;
Properties.Settings.Default.Minimised = false;
}
else if (WindowState == FormWindowState.Normal)
{
Properties.Settings.Default.Location = Location;
Properties.Settings.Default.Size = Size;
Properties.Settings.Default.Maximised = false;
Properties.Settings.Default.Minimised = false;
}
else
{
Properties.Settings.Default.Location = RestoreBounds.Location;
Properties.Settings.Default.Size = RestoreBounds.Size;
Properties.Settings.Default.Maximised = false;
Properties.Settings.Default.Minimised = true;
}
Properties.Settings.Default.Save();
It will save the current state.
Then add this code to your form's OnLoad handler:
if (Properties.Settings.Default.Maximised)
{
Location = Properties.Settings.Default.Location;
WindowState = FormWindowState.Maximized;
Size = Properties.Settings.Default.Size;
}
else if (Properties.Settings.Default.Minimised)
{
Location = Properties.Settings.Default.Location;
WindowState = FormWindowState.Minimized;
Size = Properties.Settings.Default.Size;
}
else
{
Location = Properties.Settings.Default.Location;
Size = Properties.Settings.Default.Size;
}
It will restore the last state.
It even remembers which monitor in a multi monitor set up the application was maximised to.
You'll need to save the window location and size in your application settings. Here's a good C# article to show you how.
EDIT
You can save pretty much anything you want in the application settings. In the Type column of the settings grid you can browse to any .NET type. WindowState is in System.Windows.Forms and is listed as FormWindowState. There's also a property for FormStartPosition.
I tried a few different methods; this is what ended up working for me.
(In this case - on first launch - the defaults haven't been persisted yet, so the form will use the values set in the designer)
Add the settings to the project (manually - don't rely on visual studio):
Add the following code to your form:
private void Form1_Load(object sender, EventArgs e)
{
this.RestoreWindowPosition();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.SaveWindowPosition();
}
private void RestoreWindowPosition()
{
if (Settings.Default.HasSetDefaults)
{
this.WindowState = Settings.Default.WindowState;
this.Location = Settings.Default.Location;
this.Size = Settings.Default.Size;
}
}
private void SaveWindowPosition()
{
Settings.Default.WindowState = this.WindowState;
if (this.WindowState == FormWindowState.Normal)
{
Settings.Default.Location = this.Location;
Settings.Default.Size = this.Size;
}
else
{
Settings.Default.Location = this.RestoreBounds.Location;
Settings.Default.Size = this.RestoreBounds.Size;
}
Settings.Default.HasSetDefaults = true;
Settings.Default.Save();
}
Previous solutions didn't work for me. After playing a while I ended up with following code which:
preserves maximised and normal state
replaces minimised state with default position
in case of screen size changes (detached monitor, remote connection,...) it will not get user into frustrating state with application open outside of screen.
private void MyForm_Load(object sender, EventArgs e)
{
if (Properties.Settings.Default.IsMaximized)
WindowState = FormWindowState.Maximized;
else if (Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(Properties.Settings.Default.WindowPosition)))
{
StartPosition = FormStartPosition.Manual;
DesktopBounds = Properties.Settings.Default.WindowPosition;
WindowState = FormWindowState.Normal;
}
}
private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
Properties.Settings.Default.IsMaximized = WindowState == FormWindowState.Maximized;
Properties.Settings.Default.WindowPosition = DesktopBounds;
Properties.Settings.Default.Save();
}
user settings:
<userSettings>
<WindowsFormsApplication2.Properties.Settings>
<setting name="WindowPosition" serializeAs="String">
<value>0, 0, -1, -1</value>
</setting>
<setting name="IsMaximized" serializeAs="String">
<value>False</value>
</setting>
</WindowsFormsApplication2.Properties.Settings>
</userSettings>
Note: the WindowsPosition is intentionally wrong, so during first launch application will use default location.
Note that IntersectsWith expects a Rectangle, not a Point. So unlike other answers, this answer is saving the DesktopBounds, not Location, into Properties.Settings.Default.WindowPosition
If you use the fabulous open source library - Jot, you can forget about the tedious .settings files and just do this:
public MainWindow()
{
InitializeComponent();
_stateTracker.Configure(this)
.IdentifyAs("MyMainWindow")
.AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
.RegisterPersistTrigger(nameof(Closed))
.Apply();
}
There's a Nuget package as well, and you can configure pretty much everything about how/when/where data is stored.
Disclaimer: I'm the author, but the library is completely open source (under MIT license).
Matt - to save the WindowState as a user setting, in the Settings Dialog, in the "Type" dropdown, scroll to the bottom and select "Browse".
In the "Select a Type" dialog, expand System.Windows.Forms and you can choose "FormWindowState" as the type.
(sorry, I don't see a button that allows me to comment on the comment...)
If you have more than 1 form you can use something like this...
Add this part all form load void
var AbbA = Program.LoadFormLocationAndSize(this);
this.Location = new Point(AbbA[0], AbbA[1]);
this.Size = new Size(AbbA[2], AbbA[3]);
this.FormClosing += new FormClosingEventHandler(Program.SaveFormLocationAndSize);
Save form location and size to app.config xml
public static void SaveFormLocationAndSize(object sender, FormClosingEventArgs e)
{
Form xForm = sender as Form;
Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
if (ConfigurationManager.AppSettings.AllKeys.Contains(xForm.Name))
config.AppSettings.Settings[xForm.Name].Value = String.Format("{0};{1};{2};{3}", xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height);
else
config.AppSettings.Settings.Add(xForm.Name, String.Format("{0};{1};{2};{3}", xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height));
config.Save(ConfigurationSaveMode.Full);
}
Load form location and size from app.config xml
public static int[] LoadFormLocationAndSize(Form xForm)
{
int[] LocationAndSize = new int[] { xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height };
//---//
try
{
Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
var AbbA = config.AppSettings.Settings[xForm.Name].Value.Split(';');
//---//
LocationAndSize[0] = Convert.ToInt32(AbbA.GetValue(0));
LocationAndSize[1] = Convert.ToInt32(AbbA.GetValue(1));
LocationAndSize[2] = Convert.ToInt32(AbbA.GetValue(2));
LocationAndSize[3] = Convert.ToInt32(AbbA.GetValue(3));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
//---//
return LocationAndSize;
}
You'll have to manually save the information somewhere. I'd suggest doing so as application settings, storing them in user specific isolated storage.
Once you load up, read the settings then resize/move your form.
My answer is adapted from ChrisF♦'s answer, but I've fixed one thing I didn't like - if the window is minimized at the time of closing, it would appear minimized on next start.
My code handles that case correctly by remembering whether the window was maximized or normal at the time of its minimization, and setting the persistent state accordingly.
Unfortunately, Winforms doesn't expose that information directly, so I needed to override WndProc and store it myself. See Check if currently minimized window was in maximized or normal state at the time of minimization
partial class Form1 : Form
{
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SYSCOMMAND)
{
int wparam = m.WParam.ToInt32() & 0xfff0;
if (wparam == SC_MAXIMIZE)
LastWindowState = FormWindowState.Maximized;
else if (wparam == SC_RESTORE)
LastWindowState = FormWindowState.Normal;
}
base.WndProc(ref m);
}
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MAXIMIZE = 0xf030;
private const int SC_RESTORE = 0xf120;
private FormWindowState LastWindowState;
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (WindowState == FormWindowState.Normal)
{
Properties.Settings.Default.WindowLocation = Location;
Properties.Settings.Default.WindowSize = Size;
}
else
{
Properties.Settings.Default.WindowLocation = RestoreBounds.Location;
Properties.Settings.Default.WindowSize = RestoreBounds.Size;
}
if (WindowState == FormWindowState.Minimized)
{
Properties.Settings.Default.WindowState = LastWindowState;
}
else
{
Properties.Settings.Default.WindowState = WindowState;
}
Properties.Settings.Default.Save();
}
private void Form1_Load(object sender, EventArgs e)
{
if (Properties.Settings.Default.WindowSize != new Size(0, 0))
{
Location = Properties.Settings.Default.WindowLocation;
Size = Properties.Settings.Default.WindowSize;
WindowState = Properties.Settings.Default.WindowState;
}
}
You could also save it in your (let's say) config.xml when you close the form:
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
XmlDocument docConfigPath = new XmlDocument();
docConfigPath.Load(XML_Config_Path);
WriteNode(new string[] { "config", "Size", "Top", Top.ToString() }, docConfigPath);
WriteNode(new string[] { "config", "Size", "Left", Left.ToString() }, docConfigPath);
WriteNode(new string[] { "config", "Size", "Height", Height.ToString() }, docConfigPath);
WriteNode(new string[] { "config", "Size", "Width", Width.ToString() }, docConfigPath);
docConfigPath.Save(XML_Config_Path);
}
public static XmlNode WriteNode(string[] sNode, XmlDocument docConfigPath)
{
int cnt = sNode.Length;
int iNode = 0;
string sNodeNameLast = "/" + sNode[0];
string sNodeName = "";
XmlNode[] xN = new XmlNode[cnt];
for (iNode = 1; iNode < cnt - 1; iNode++)
{
sNodeName = "/" + sNode[iNode];
xN[iNode] = docConfigPath.SelectSingleNode(sNodeNameLast + sNodeName);
if (xN[iNode] == null)
{
xN[iNode] = docConfigPath.CreateNode("element", sNode[iNode], "");
xN[iNode].InnerText = "";
docConfigPath.SelectSingleNode(sNodeNameLast).AppendChild(xN[iNode]);
}
sNodeNameLast += sNodeName;
}
if (sNode[cnt - 1] != "")
xN[iNode - 1].InnerText = sNode[cnt - 1];
return xN[cnt - 2];
}
And the loading is on your:
private void Form1_Load(object sender, EventArgs e)
{
XmlDocument docConfigPath = new XmlDocument();
docConfigPath.Load(XML_Config_Path);
XmlNodeList nodeList = docConfigPath.SelectNodes("config/Size");
Height = ReadNodeInnerTextAsNumber("config/Size/Height", docConfigPath);
Width = ReadNodeInnerTextAsNumber("config/Size/Width", docConfigPath);
Top = ReadNodeInnerTextAsNumber("config/Size/Top", docConfigPath);
Left = ReadNodeInnerTextAsNumber("config/Size/Left", docConfigPath);
}
The config.xml should contain the following:
<?xml version="1.0" encoding="utf-8"?>
<config>
<Size>
<Height>800</Height>
<Width>1400</Width>
<Top>100</Top>
<Left>280</Left>
</Size>
</config>
I've been using this method so far and it's been working great. You don't have to fiddle around with application settings. Instead, it uses serialization to write a settings file to your working directory. I use JSON, but you can use .NET's native XML serialization or any serialization for that matter.
Put these static methods in a common extensions class. Bonus points if you have a common extensions project that you reference by multiple projects:
const string WINDOW_STATE_FILE = "windowstate.json";
public static void SaveWindowState(Form form)
{
var state = new WindowStateInfo
{
WindowLocation = form.Location,
WindowState = form.WindowState
};
File.WriteAllText(WINDOW_STATE_FILE, JsonConvert.SerializeObject(state));
}
public static void LoadWindowState(Form form)
{
if (!File.Exists(WINDOW_STATE_FILE)) return;
var state = JsonConvert.DeserializeObject<WindowStateInfo>(File.ReadAllText(WINDOW_STATE_FILE));
if (state.WindowState.HasValue) form.WindowState = state.WindowState.Value;
if (state.WindowLocation.HasValue) form.Location = state.WindowLocation.Value;
}
public class WindowStateInfo
{
public FormWindowState? WindowState { get; set; }
public Point? WindowLocation { get; set; }
}
You only need to write that code once and never mess with again. Now for the fun part: Put the below code in your form's Load and FormClosing events like so:
private void Form1_Load(object sender, EventArgs e)
{
WinFormsGeneralExtensions.LoadWindowState(this);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
WinFormsGeneralExtensions.SaveWindowState(this);
}
That is all you need to do. The only setup is getting those extensions into a common class. After that, just add two lines of code to your form's code and you're done.
This code will only really work if your WinForm's app has a single form. If it has multiple forms that you want to remember the positions of, you'll need to get creative and do something like this:
public static void SaveWindowState(Form form)
{
var state = new WindowStateInfo
{
WindowLocation = form.Location,
WindowState = form.WindowState
};
File.WriteAllText($"{form.Name} {WINDOW_STATE_FILE}", JsonConvert.SerializeObject(state));
}
I only save location and state, but you can modify this to remember form height and width or anything else. Just make the change once and it will work for any application that calls it.