I want to pinpoint I'm totally new to C# and I'm just testing around to get a grasp of this language.
Want I want to achieve is to just console-print in a secondary window a couple of values I've set in the MainWindow.
This is a function contained in the MainWindow class, triggered by a button click.
private void ValidationExecuted(object sender, ExecutedRoutedEventArgs eventArgs)
{
// If the validation was successful, let's open a new window.
GeneratorWindow generatorWindow = new GeneratorWindow();
generatorWindow.TextBlockName1.Text = this.tbPoints.Text;
generatorWindow.TextBlockName2.Text = this.tbPDC.Text;
int.TryParse(this.tbPoints.Text, out int numberOfPoints);
int.TryParse(this.tbPDC.Text, out int pdc);
// Those lines correctly print the values I've inserted in the TextBoxes.
Console.WriteLine(numberOfPoints);
Console.WriteLine(pdc);
generatorWindow.NumberOfPoints = numberOfPoints;
generatorWindow.MainPDC = pdc;
generatorWindow.Show();
// Resetting the UI.
this.validator = new Validator();
this.grid.DataContext = this.validator;
eventArgs.Handled = true;
}
Now my secondary window:
public partial class GeneratorWindow : Window
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:ABB_Rapid_Generator.GeneratorWindow" /> class.
/// </summary>
public GeneratorWindow()
{
this.InitializeComponent();
// Those lines just print a pair of 0.
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
/// <summary>
/// Gets or sets the number of points.
/// </summary>
public int NumberOfPoints { private get; set; }
/// <summary>
/// Gets or sets the main PDC.
/// </summary>
public int MainPDC { private get; set; }
}
As you can see in the code comments, the Console.WriteLine() contained in the main class are correctly working. Moreover I can assign my custom values to the TextBlocks contained in the other class. On the contrary, the Console.WriteLine() lines in the secondary class are just outputting a couple of zeros.
What have I been missing?
The problem is that in GeneratorWindow you are writing to the console in the constructor method, so the values are being output before you are changing them.
The only way you can really get that output to work would be to pass the values as parameters of the constructor and set them (in the constructor) before you do the console output. Though there doesn't seem any logical reason to go down that path.
For example:
public GeneratorWindow(int numberOfPoints, int mainPdc)
{
this.InitializeComponent();
this.NumberOfPoints = numberOfPoints;
this.MainPDC = mainPdc;
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
Alternatively, if you want to see the values after you set them, then you will need to move your console outputs to another function that you call after you have set the values.
For example, add this function to GeneratorWindow:
public void OutputValues()
{
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
Which you can then call after you have set the values in your other class:
GeneratorWindow generatorWindow = new GeneratorWindow();
generatorWindow.NumberOfPoints = numberOfPoints;
generatorWindow.MainPDC = pdc;
generatorWindow.OutputValues();
you can add a parameter-ize constructor to do so
public partial class GeneratorWindow : Window
{
//Private members
int m_numberOfPoints;
int m_mainPDC;
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:ABB_Rapid_Generator.GeneratorWindow" /> class.
/// </summary>
public GeneratorWindow(int mainPDC,int numberOfPoints)
{
this.InitializeComponent();
this.m_mainPDC = mainPDC;
this.m_numberOfPoints = numberOfPoints;
}
/// <summary>
/// Gets or sets the number of points.
/// </summary>
public int NumberOfPoints
{
get{ return m_numberOfPoints; }
set{ m_numberOfPoints = values; }
}
/// <summary>
/// Gets or sets the main PDC.
/// </summary>
public int MainPDC
{
get{ return m_mainPDC; }
set{ m_mainPDC= values; }
}
public void Print()
{
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
}
also this is a constructor so it will be just called at
GeneratorWindow generatorWindow = new GeneratorWindow();//here
Change the secondary window call to
generatorWindow = new GeneratorWindow(pdc,numberOfPoints);
generatorWindow.Print();
Also, your code is not done in a good way IMO, why set values like this?
generatorWindow.TextBlockName1.Text = this.tbPoints.Text;
generatorWindow.TextBlockName2.Text = this.tbPDC.Text;
If you have private variables set just as above sample you can perform all converting, printing and , receiving console output in same class.you'll need to just call the constructor and print method.
Answer above is correct, but there is an alternative.
You can use setter methods like setNumberOfPoints, setMainPDC and print co console after setting the value. So in ValidationExecuted you call for a function to set variable, and in that function after setting variable you print it to console. But don't forget to remove printing to console from constructor
Related
I've been working on this for a bit, and I've been stuck at this one spot, I can't figure out a way to count the points in the board...
So, first of all, this is a part of the code that makes the cases, in which my tokens are in:
class Case:Control
{
public Point Position { get; set; }
/// <summary>
/// Creates the dimensions for the cases
/// </summary>
public Case()
{
MaximumSize = new Size(50, 50);
MinimumSize = new Size(50, 50);
}
/// <summary>
/// Creates the background for the cases
/// </summary>
public enum DifferentCase
{
Dark,
Pale,
Brown
}
/// <summary>
/// Creates the tokens
/// </summary>
public enum Token
{
Nothing,
White,
Black
}
public DifferentCase ColorCase { get; set; }
public Token ColorToken { get; set; }
public bool IsBlack { get; set; }
And those being my tokens, I have a method that I'm trying to make that counts how many tokens are black and how many are white:
private void CheckPoints(Case cases)
{
foreach (Case case_ in cases.Controls)
{
if (case_.ColorToken == Case.Token.Black)
{
_player1Points++;
lbl_player1Points.Text = _player1Points.ToString();
}
else if (case_.ColorToken == Case.Token.White)
{
_player2Points++;
lbl_player2Points.Text = _player2Points.ToString();
}
}
}
But when I try to call that method in like this: CheckPoints() for example, if I'm clicking on of those cases, it tells me that "There is no argument that corresponds to the required formal parameter "cases" of 'frm_othello.CheckPoints(Case)'"
I don't know if the code that I put in that method is good, neither I don't know why I cant call that method in.
Currently you can't call CheckPoints(); with no arguments. You have defined it with a parameter Case
so you need to do CheckPoints(cases) or whatever is the name of the variable / property / field that has cases
My friend and I have been working on a game and we have a trace listener set up, here's some of the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
namespace GuiGame {
/// <summary>
/// A type of Trace Listener that sends its output to a ListBox.
/// </summary>
public class ListBoxTraceListener : TraceListener {
private ListBox listBox; // A reference to the listbox that we're writing to.
private string stringToAddToListBox = "";
private const char NEW_LINE = '\n';
/// <summary>
/// Parameterless constructor.
/// Do not want the generic default constructor to be used
/// as there is no way to set the ListBoxTraceListener's data.
/// This replaces the compiler's generic default constructor.
/// Pre: none
/// Post: ALWAYS throws an ArgumentException.
/// </summary>
/// <remarks>NOT TO BE USED!</remarks>
public ListBoxTraceListener() {
throw new ArgumentException("Parameterless constructor invalid.");
} // end ListBoxTraceListener constructor
/// <summary>
/// Constructor with initialising parameters.
/// Pre: the existence of a ListBox on a GUI form.
/// Post: initialised object.
/// </summary>
/// <param name="listBox">The ListBox that we're writing to.</param>
public ListBoxTraceListener(ListBox listBox) {
this.listBox = listBox;
}
/// <summary>
/// Automatically collects the outputs from all Trace.WriteLine statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox.
/// </summary>
/// <param name="s"></param>
public override void WriteLine(string s) {
Write(s + NEW_LINE);
} //end WriteLine
/// <summary>
/// Automatically collects the outputs from all Trace.Write statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox, once we receive a NEW_LINE.
/// </summary>
/// <param name="s"></param>
public override void Write(string s) {
stringToAddToListBox += s;
// If we have one or more complete lines
if (stringToAddToListBox.Contains (NEW_LINE)) {
// Split the string into multiple lines.
// If NEW_LINE is found at the beginning or end of the string,
// then the corresponding array element contains an empty string.
string[] lines = stringToAddToListBox.Split(NEW_LINE);
// Add all the lines to the listbox, except for the last one.
// When stringToAddToListBox has a new-line at the end,
// the last element in lines[] will be an empty string.
int highestLineNumber = lines.Length - 1;
for (int i = 0; i < highestLineNumber; i++) {
AddToListBox(lines[i]);
}
// Reset stringToAddToListBox to what remains. (May be an empty string).
stringToAddToListBox = lines[highestLineNumber];
}
} // end Write
/// <summary>
/// Adds a complete output-line to the ListBox.
/// Pre: none.
/// Post: the string listBoxLine is displayed in the listBox .
/// </summary>
/// <param name="listBoxLine"></param>
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
} // end AddToListBox
}
}
At this stage we are just trying to use the trace listener to output some text on in the ListBox so we know it is working, so we have an event handler setup:
private void RollDiceButton_Click(object sender, EventArgs e)
{
}
We haven't been able to get any output from the trace listener. The Add method as the trace listener is not set up for that. Can anyone provide some suggestions please? I think maybe we are doing something really stupid and obvious that we have missed.
The most probable cause of your issue is that your application is single threaded (as most simple Windows applications are). This means that although you are sending messages to the listview to append a new element to the list, the message is not yet handled just because your original handler hasn't returned yet (the RollDiceButton_Click).
To work this around, you should force the list to refresh itself from within the current handler:
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
listBox.Refresh();
} // end
If this doesn't help, try temporarily switching to unconditional processing of all pending events with
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
Application.DoEvents();
} // end
and report back.
I have a form that represents a USB device Terminal that has been giving me some errors. After half a day of debugging strange errors with no known source I somehow found out that the Terminal does not function when it is instantiated but not shown. When I change the code and add usbTerminal.Show();, then it works properly.
USBTerminal usbTouchTerminal;
public MainForm()
{
InitializeComponent();
USBSettings usbTouchSettings = new USBSettings();
usbTouchTerminal = new USBTerminal(usbTouchSettings); //Create Terminal with settings
usbTouchTerminal.StartUSB();
usbTouchTerminal.Show(); //works ONLY when show is here
}
How is this possible and why? I've done a massive search and none of my code depends on the .Visible property on either my Terminal or main form?
I'm completely baffled on why some form would not work if it isn't shown. MSDN or google wasn't really a help either. I was certain it would function properly when instantiated but not shown.
PS. I added
usbTerminal.Show();
usbTerminal.Hide();
and the Terminal functioned correctly.
Thank you for any help!
EDIT:
I should also note that this usbTerminal uses the WndProc override. I'm not an expert on that, but I feel that it may have something to do with it.
I should note that this is LibUSBdotnet
public class USBSettings
{
/// <summary>
/// This is the Vender ID Number. (0x0B6A)
/// </summary>
public ushort VID { get; set; }
/// <summary>
/// This is the Product ID Number. (0x5346)
/// </summary>
public ushort PID { get; set; }
/// <summary>
/// This is the optional Serial Name. ("")
/// </summary>
public string SerialName { get; set; }
/// <summary>
/// This is the Reader USB Endpoint. (ReadEndpointID.Ep02)
/// </summary>
public ReadEndpointID ReaderEndpoint { get; set; }
/// <summary>
/// This is the Writer USB Endpoint. (WriteEndpointID.Ep01)
/// </summary>
public WriteEndpointID WriterEndpoint { get; set; }
/// <summary>
/// This is the Registry Key for USB settings. ("SOFTWARE\\DEFAULT\\USBPROPERTIES")
/// </summary>
public string SubKey { get; set; }
/// <summary>
/// This is the default read buffer size for the USB Device.
/// </summary>
public int ReadBufferSize { get; set; }
/// <summary>
/// This constructor houses default values for all properties.
/// </summary>
public USBSettings()
{
VID = 0x0B6A;
PID = 0x5346;
SerialName = "";
ReaderEndpoint = ReadEndpointID.Ep02;
WriterEndpoint = WriteEndpointID.Ep01;
SubKey = "SOFTWARE\\DEFAULT\\USBPROPERTIES";
ReadBufferSize = 100;
}
}
The question is poorly documented but this is fairly normal for code that works with devices. They tend to need to know about Plug & Play events and that requires a top-level window to be created that receives the WM_DEVICECHANGE notification message. Creating a .NET Form object isn't enough, you also have to create the native window for it. Which, in typical .NET lazy fashion, happens at the last possible moment, when you force the window to be visible. Either by calling the Show() method or setting the Visible property to true. The window doesn't actually have to be visible to get the Plug & Play notifications.
You can get the window created without also making it visible. That requires modifying the USBTerminal class. Paste this code:
protected override void SetVisibleCore(bool value) {
if (!this.IsHandleCreated) {
this.CreateHandle();
value = false;
}
base.SetVisibleCore(value);
}
And call the Show() method as normal. Beware that the Load event won't fire until the window actually becomes visible so if necessary move any code in the event handler to this method. If this is not the primary window for the app, in other words not the one that's passed to Application.Run() in your Main() method, then you can make do with simply calling this.CreateHandle() as the last statement in the form constructor. In which case calling Show() is no longer necessary.
I suspect this is because the underlying window is not created before you call Show(). Since the window isn't created, your custom WndProc isn't called.
To verify, you can create the window without showing it - by looking at the Handle property. As the documentation says - if the handle has not been created by the time you call, it will be created. Try it, I bet it'll work just as if you called Show and then Hide.
It is very hard to tell from the information you have but I think you are using a form where a class should be used. You should rethink your program structure and re-write this as a class to hold and transmit the data as you need. As some of the other have pointed out the listbox and/or other function are not running until the form is shown and the methods is executed.
Because some required functions will be called when Form onShow event called.
I work as a web developer with a web designer and we usually do like this :
- I create the system , I generate some Xml files
- the designer display the xml files with xslt
Nothing new.
My problem is that I use Xml Serialization to create my xml files, but I never use Deserialization. So I'd like to know if there is a way to avoid fix like these :
empty setter for my property
empty parameter-less constructor
implement IXmlSerializable and throw "notimplementedexception" on deserialization
do a copy of the class with public fields
Ok mis-read your question first time around! Pretty sure there is no way to avoid this. There has to be a parameterless constructor and you can't serialize readonly properties. I think your only other option is DataContractSerializer.
http://blogs.mastronardi.be/Sandro/2007/08/22/CustomXMLSerializerBasedOnReflectionForSerializingPrivateVariables.aspx
This article describes creating a custom XML serialiser so you can serialise private fields - it may take a little bit of moulding to the form that you want, but it's easier than it looks (honest!) and it's a good start to writing your own serialiser / deserialiser that will serialise exactly what you want - and doesn't care about parameterless constructors or writeable properties.
The only other solution I can think of is to make a wrapper class for every serialisable class - but I don't know how good that would be in the long run. I just get the impression it's not good.
I know you don't want to add a parameterless constructor nor setters, but that's the only way to go with using the XmlSerializer. The good news is the parameterless constructor can be private and the setters can be empty and serialization will work. See thus:
namespace Aesop.Dto
{
using System;
using System.Xml.Serialization;
/// <summary>
/// Represents an Organization ID/Name combination.
/// </summary>
[Serializable]
public sealed class Org
{
/// <summary>
/// The organization's name.
/// </summary>
private readonly string name;
/// <summary>
/// The organization's ID.
/// </summary>
private readonly int id;
/// <summary>
/// Initializes a new instance of the <see cref="Org"/> class.
/// </summary>
/// <param name="name">The organization's name.</param>
/// <param name="id">The organization's ID.</param>
public Org(string name, int id)
{
this.name = name;
this.id = id;
}
/// <summary>
/// Prevents a default instance of the <see cref="Org"/> class from
/// being created.
/// </summary>
private Org()
{
}
/// <summary>
/// Gets or sets the organization's name.
/// </summary>
/// <value>The organization's name.</value>
[XmlAttribute]
public string Name
{
get
{
return this.name;
}
set
{
}
}
/// <summary>
/// Gets or sets the organization's ID.
/// </summary>
/// <value>The organization's ID.</value>
[XmlAttribute]
public int ID
{
get
{
return this.id;
}
set
{
}
}
}
}
Ok now i understand it. I don't think there can be any way to do it with XMLSerialization.
XMLSerialization need these information to re-populate the object. It does not know that some user never deserialize data. You might have to write some code to generate XML for your objects.
class Preferences
{
private const string filePreferences = "preferences.xml";
public Preferences() { }
public static Preferences Load()
{
Preferences pref = null;
if (File.Exists(Preferences.FileFullPath))
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(Preferences));
using (var xmlReader = new System.Xml.XmlTextReader(Preferences.FileFullPath))
{
if (serializer.CanDeserialize(xmlReader))
{
pref = serializer.Deserialize(xmlReader) as Preferences;
}
}
}
return ((pref == null) ? new Preferences() : pref);
}
public void Save()
{
var preferencesFile = FileFullPath;
var preferencesFolder = Directory.GetParent(preferencesFile).FullName;
using (var fileStream = new FileStream(preferencesFile, FileMode.Create))
{
new System.Xml.Serialization.XmlSerializer(typeof(Preferences)).Serialize(fileStream, this);
}
}
}
I have made a class which a form can inherit from and it handles form Location, Size and State. And it works nicely. Except for one thing:
When you maximize the application on a different screen than your main one, the location and size (before you maximized) gets stored correctly, but when it is maximized (according to its previous state) it is maximized on my main monitor. When I then restore it to normal state, it goes to the other screen where it was before. When I then maximize it again, it of course maximized on the correct screen.
So my question is... how can I make a form, when it is maximized, remember what screen it was maximized on? And how do I restore that when the form opens again?
Kind of complete solution to problem
I accepted the answer which had a very good tip about how to if on screen. But that was just part of my problem, so here is my solution:
On load
First get stored Bounds and WindowState from whatever storage.
Then set the Bounds.
Make sure Bounds are visible either by Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds)) or MdiParent.Controls.OfType<MdiClient>().First().ClientRectangle.IntersectsWith(Bounds).
If it doesn't, just do Location = new Point();.
Then set window state.
On closing
Store WindowState.
If WindowState is FormWindowState.Normal, then store Bounds, otherwise store RestoreBounds.
And thats it! =)
Some example code
So, as suggested by Oliver, here is some code. It needs to be fleshed out sort of, but this can be used as a start for whoever wants to:
PersistentFormHandler
Takes care of storing and fetching the data somewhere.
public sealed class PersistentFormHandler
{
/// <summary>The form identifier in storage.</summary>
public string Name { get; private set; }
/// <summary>Gets and sets the window state. (int instead of enum so that it can be in a BI layer, and not require a reference to WinForms)</summary>
public int WindowState { get; set; }
/// <summary>Gets and sets the window bounds. (X, Y, Width and Height)</summary>
public Rectangle WindowBounds { get; set; }
/// <summary>Dictionary for other values.</summary>
private readonly Dictionary<string, Binary> otherValues;
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, int defaultWindowState, Rectangle defaultWindowBounds)
: this(windowType, null, defaultWindowState, defaultWindowBounds) { }
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as base <see cref="Name"/>.</param>
/// <param name="id">Use this if you need to separate windows of same type. Will be appended to <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, string id, int defaultWindowState, Rectangle defaultWindowBounds)
{
Name = string.IsNullOrEmpty(id)
? windowType.FullName
: windowType.FullName + ":" + id;
WindowState = defaultWindowState;
WindowBounds = defaultWindowBounds;
otherValues = new Dictionary<string, Binary>();
}
/// <summary>
/// Looks for previously stored values in database.
/// </summary>
/// <returns>False if no previously stored values were found.</returns>
public bool Load()
{
// See Note 1
}
/// <summary>
/// Stores all values in database
/// </summary>
public void Save()
{
// See Note 2
}
/// <summary>
/// Adds the given <paramref key="value"/> to the collection of values that will be
/// stored in database on <see cref="Save"/>.
/// </summary>
/// <typeparam key="T">Type of object.</typeparam>
/// <param name="key">The key you want to use for this value.</param>
/// <param name="value">The value to store.</param>
public void Set<T>(string key, T value)
{
// Create memory stream
using (var s = new MemoryStream())
{
// Serialize value into binary form
var b = new BinaryFormatter();
b.Serialize(s, value);
// Store in dictionary
otherValues[key] = new Binary(s.ToArray());
}
}
/// <summary>
/// Same as <see cref="Get{T}(string,T)"/>, but uses default(<typeparamref name="T"/>) as fallback value.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <returns>The stored object, or the default(<typeparamref name="T"/>) object if something went wrong.</returns>
public T Get<T>(string key)
{
return Get(key, default(T));
}
/// <summary>
/// Gets the value identified by the given <paramref name="key"/>.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <param name="fallback">Value to return if the given <paramref name="key"/> could not be found.
/// In other words, if you haven't used <see cref="Set{T}"/> yet.</param>
/// <returns>The stored object, or the <paramref name="fallback"/> object if something went wrong.</returns>
public T Get<T>(string key, T fallback)
{
// If we have a value with this key
if (otherValues.ContainsKey(key))
{
// Create memory stream and fill with binary version of value
using (var s = new MemoryStream(otherValues[key].ToArray()))
{
try
{
// Deserialize, cast and return.
var b = new BinaryFormatter();
return (T)b.Deserialize(s);
}
catch (InvalidCastException)
{
// T is not what it should have been
// (Code changed perhaps?)
}
catch (SerializationException)
{
// Something went wrong during Deserialization
}
}
}
// Else return fallback
return fallback;
}
}
Note 1: In the load method you have to look for previously stored WindowState, WindowBounds and other values. We use SQL Server, and have a Window table with columns for Id, Name, MachineName (for Environment.MachineName), UserId, WindowState, X, Y, Height, Width. So for every window, you would have one row with WindowState, X, Y, Height and Width for each user and machine. In addition we have a WindowValues table which just has a foreign key to WindowId, a Key column of type String and a Value column of type Binary. If there is stuff that is not found, I just leave things default and return false.
Note 2: In the save method you then, of course do the reverse from what you do in the Load method. Creating rows for Window and WindowValues if they don't exist already for the current user and machine.
PersistentFormBase
This class uses the previous class and forms a handy base class for other forms.
// Should have been abstract, but that makes the the designer crash at the moment...
public class PersistentFormBase : Form
{
private PersistentFormHandler PersistenceHandler { get; set; }
private bool handlerReady;
protected PersistentFormBase()
{
// Prevents designer from crashing
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
Load += persistentFormLoad;
FormClosing += persistentFormFormClosing;
}
}
protected event EventHandler<EventArgs> ValuesLoaded;
protected event EventHandler<EventArgs> StoringValues;
protected void StoreValue<T>(string key, T value)
{
if (!handlerReady)
throw new InvalidOperationException();
PersistenceHandler.Set(key, value);
}
protected T GetValue<T>(string key)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get<T>(key);
}
protected T GetValue<T>(string key, T fallback)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get(key, fallback);
}
private void persistentFormLoad(object sender, EventArgs e)
{
// Create PersistenceHandler and load values from it
PersistenceHandler = new PersistentFormHandler(GetType(), (int) FormWindowState.Normal, Bounds);
PersistenceHandler.Load();
handlerReady = true;
// Set size and location
Bounds = PersistenceHandler.WindowBounds;
// Check if we have an MdiParent
if(MdiParent == null)
{
// If we don't, make sure we are on screen
if (!Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds)))
Location = new Point();
}
else
{
// If we do, make sure we are visible within the MdiClient area
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault();
if(c != null && !c.ClientRectangle.IntersectsWith(Bounds))
Location = new Point();
}
// Set state
WindowState = Enum.IsDefined(typeof (FormWindowState), PersistenceHandler.WindowState) ? (FormWindowState) PersistenceHandler.WindowState : FormWindowState.Normal;
// Notify that values are loaded and ready for getting.
var handler = ValuesLoaded;
if (handler != null)
handler(this, EventArgs.Empty);
}
private void persistentFormFormClosing(object sender, FormClosingEventArgs e)
{
// Set common things
PersistenceHandler.WindowState = (int) WindowState;
PersistenceHandler.WindowBounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds;
// Notify that values will be stored now, so time to store values.
var handler = StoringValues;
if (handler != null)
handler(this, EventArgs.Empty);
// Save values
PersistenceHandler.Save();
}
}
And thats pretty much it. To use it, a form would just inherit from the PersistentFormBase. That would automatically take care of bounds and state. If anything else should be stored, like a splitter distance, you would listen for the ValuesLoaded and StoringValues events and in those use the GetValue and StoreValue methods.
Hope this can help someone! Please let me know if it does. And also, please provide some feedback if there is anything you think could be done better or something. I would like to learn =)
There's no built in way to do this - you'll have to write the logic yourself. One reason for this is that you have to decide how to handle the case where the monitor that the window was last shown on is no longer available. This can be quite common with laptops and projectors, for example. The Screen class has some useful functionality to help with this, although it can be difficult to uniquely and consistently identify a display.
I found a solution to your problem by writing a little functio, that tests, if a poitn is on a connected screen.
The main idea came from
http://msdn.microsoft.com/en-us/library/system.windows.forms.screen(VS.80).aspx
but some modifications were needed.
public static bool ThisPointIsOnOneOfTheConnectedScreens(Point thePoint)
{
bool FoundAScreenThatContainsThePoint = false;
for(int i = 0; i < Screen.AllScreens.Length; i++)
{
if(Screen.AllScreens[i].Bounds.Contains(thePoint))
FoundAScreenThatContainsThePoint = true;
}
return FoundAScreenThatContainsThePoint;
}
There are a few issues with the above solution.
On multiple screens as well as if the restore screen is smaller.
It should use Contains(...), rather than IntersectsWith as the control part of the form might otherwise be outside the screen-area.
I will suggest something along these lines
bool TestBounds(Rectangle R) {
if (MdiParent == null) return Screen.AllScreens.Any(ø => ø.Bounds.Contains(R)); // If we don't have an MdiParent, make sure we are entirely on a screen
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault(); // If we do, make sure we are visible within the MdiClient area
return (c != null && c.ClientRectangle.Contains(R));
}
and used like this. (Note that I let Windows handle it if the saved values does not work)
bool BoundsOK=TestBounds(myBounds);
if (!BoundsOK) {
myBounds.Location = new Point(8,8); // then try (8,8) , to at least keep the size, if other monitor not currently present, or current is lesser
BoundsOK = TestBounds(myBounds);
}
if (BoundsOK) { //Only change if acceptable, otherwise let Windows handle it
StartPosition = FormStartPosition.Manual;
Bounds = myBounds;
WindowState = Enum.IsDefined(typeof(FormWindowState), myWindowState) ? (FormWindowState)myWindowState : FormWindowState.Normal;
}
Try to spawn your main form in its saved location in restored (non-maximized) state, THEN maximize it if the last state was maximized.
As Stu said, be careful about removed monitors in this case. Since the saved location may contain off-screen coordinates (even negative ones), you may effectively end up with and invisible (off-screen, actually) window. I think checking for desktop bounds before loading previous state should prevent this.