using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using TradeIdeas.XML;
/* Typically the main program would make one instance of the Layout Manager.
*
* Each window type is represented by a string. This string must be saved when you are
* saving a window, so we know which procedure to call when restoring a window. The
* rest is completely up to the window, but most windows will call SaveBase() and
* RestoreBase() to do the common things like the size and position of the window.
* The mechanism doesn't even require a form; you can choose to save other settings
* as part of the layout.
*
* If you want to be saved as part of the layout, you have two choices. You can
* implement ISaveLayout. When the user askes to save the layout, we iterate though
* all open windows looking for windows that implement that interface. That is ideal
* for classes which can support any number of windows. We also have our own list of
* windows named SpecialSaveLayout. This is useful for windows which are unique, and
* which might be hidden at the time we save the layout. These will still have the
* chance save their layout.
*
* Note that the callback for saving your layout gives you an XML node. That is the
* parent of any nodes you create. You should create one child per window that you
* want to save. This gives you the opportunity to decide at runtime not to save
* a specific window. Or to save more than one window in one callback.
*
* All restoring is done through AddRestoreRule(). The key is the name that we
* stored in the XML when we saved the layout. Each key points to a delegate. For
* a class which can support any number of windows, this delegate will typically
* call the class's constructor, then RestoreBase(), then anything specific for
* that window. For unique windows, the delegate will point directly to an existing
* window object, so there is no need to call the constructor. The delegate will
* immedately call RestoreBase() followed by anything specific to the object.
*
* Notice that we always store a list of windows. Presumably when you say to save
* a window, you will get a file with only that one window. However, there is
* no restriction, and when you say to load a window, you might get multiple windows.
* In fact, the only difference between loading a saved window and loading a saved
* layout is that before we restore an old layout, we close all the existing windows.
*
* Notice that there is a way to ignore the position when we restore a window. That
* makes a convenient way to duplicate a window. Save the window into memory, then
* restore everything but the position. Windows should ask the operating system to
* put them in the default position when they are first created. (For some reason
* there is no method to request the default position after a window has been
* created.) When you duplicate a window, you don't want the new to be on top of
* the old one. That's confusing to a user; it looks like nothing happened. The
* default position will be offset by a little bit each time you create a new window
* so you can see each window clearly.
*
* This code was specifically designed to know nothing about the specific windows
* in the program. It learns about them at runtime through callbacks. So many
* different main programs should all be able to use this library routine. This
* code is based loosely on a similar library I wrote for Delphi.
*/
namespace TradeIdeas.TIQ
{
///
/// This delegate works with . This allows
/// any class to participate in the layout. is an alternative.
/// This delegate works well with classes that are not forms, and with forms where you
/// want exactly 0 or 1 instance of each form to be visible.
///
///
/// This is the whole layout file. Create one new node for each window.
/// provides a good starting point for saving a window.
///
public delegate void SaveLayout(XmlNode parent);
///
/// A window should implement this interface if it wants to participate in the layout. In particular,
/// this is used when there can be 0 or more windows of a particular type. We iterate through the
/// list of visible windows looking for windows that implement this. is a
/// reasonable alternative.
///
public interface ISaveLayout
{
///
/// Add yourself to the layout.
/// provides a good starting point for saving a window.
///
///
/// This is the whole layout file. Create one new node for each window.
///
void SaveLayout(XmlNode parent);
}
public delegate void LayoutRestoreCompleteHandler();
public delegate void RestoreLayout(XmlNode description, bool ignorePosition, bool cascadePosition);
public class LayoutManager
{
public const string FORM_TYPE = "FORM_TYPE";
const string BASE = "BASE";
const int CASCADE_OFFSET = 20;
public static XmlNode SaveBase(XmlNode parent, Form toSave, string formType)
{ // Start by creating the new node for the window, as that will be a common
// activity.
XmlNode result = parent.NewNode("WINDOW");
// FORM_TYPE is required.
result.SetProperty(FORM_TYPE, formType);
// Put all of these properties in a sub tag, so we keep the namespace clear.
// We might want to add new properties later, and we don't want their names
// to conflict with names used by the individual windows.
XmlNode baseNode = result.NewNode(BASE);
// Save the position of the windows. For Mimimized and Maximized windows
// use RestoreBounds property to retrieve size and position.
if (toSave.WindowState == FormWindowState.Minimized || toSave.WindowState == FormWindowState.Maximized)
{
baseNode.SetProperty("Top", toSave.RestoreBounds.Top);
baseNode.SetProperty("Left", toSave.RestoreBounds.Left);
baseNode.SetProperty("Height", toSave.RestoreBounds.Height);
baseNode.SetProperty("Width", toSave.RestoreBounds.Width);
}
else
{
baseNode.SetProperty("Top", toSave.Top);
baseNode.SetProperty("Left", toSave.Left);
baseNode.SetProperty("Height", toSave.Height);
baseNode.SetProperty("Width", toSave.Width);
}
baseNode.SetProperty("WindowState", toSave.WindowState);
baseNode.SetProperty("Visible", toSave.Visible);
return result;
}
public static void RestoreBase(XmlNode description, Form toRestore, bool ignorePosition, bool cascadePosition)
{
XmlNode baseNode = description.Node("BASE");
bool visible = baseNode.Property("Visible", true);
if (!visible)
toRestore.Visible = false;
if (!ignorePosition)
{
if (cascadePosition)
{
toRestore.Top = baseNode.Property("Top", toRestore.Top) + CASCADE_OFFSET;
toRestore.Left = baseNode.Property("Left", toRestore.Left) + CASCADE_OFFSET;
}
else
{
toRestore.Top = baseNode.Property("Top", toRestore.Top);
toRestore.Left = baseNode.Property("Left", toRestore.Left);
}
}
toRestore.Height = baseNode.Property("Height", toRestore.Height);
toRestore.Width = baseNode.Property("Width", toRestore.Width);
MakeFullyVisible(toRestore);
toRestore.WindowState = baseNode.PropertyEnum("WindowState", toRestore.WindowState);
try
{
// TryParse requires dot net 4.0.
FormWindowState windowState =
(FormWindowState)Enum.Parse(typeof(FormWindowState), baseNode.Property("WindowState"));
toRestore.WindowState = windowState;
}
catch
{
// by default keep the current value, i.e. do nothing.
}
if (visible)
// If we want the window to be invisible, do that first. If we want to window to be
// visible, do that last. If we are changing the visibity, and we are changing other
// attributes (like the size or position) we want the window to be invisible while the
// other changes are being made. Experience shows that things look a lot cleaner that
// way.
toRestore.Visible = true;
}
public event SaveLayout SpecialSaveLayout;
///
/// This is like except that the caller can specify which items
/// to include in the file. Remember that a window file and a layout file have exactly
/// the same format. If it'w important to know which is which, that information must
/// be saved elsewhere. (The traditional "Save Layout..." and "Save Window..." options
/// use the file extension to store this. The cloud puts this into a field in the
/// database.)
///
///
/// If this is true we save the position of the main window, the symbol list window,
/// etc. The exact list of what's saved comes from ,
/// so it is extensible.
///
///
/// The list of windows to save.
///
/// An XML file describing the layout. This is in a format suitable for sending to .
public XmlDocument SaveSome(bool includeSpecial, IEnumerable windows)
{
XmlDocument result = new XmlDocument();
XmlNode container = result.CreateElement("LAYOUT");
result.AppendChild(container);
foreach (ISaveLayout saveable in windows)
{
if (null != saveable)
try
{
saveable.SaveLayout(container);
}
catch { }
}
if (includeSpecial && (null != SpecialSaveLayout))
try
{
SpecialSaveLayout(container);
}
catch { }
return result;
}
public XmlDocument SaveAll()
{
XmlDocument result = new XmlDocument();
XmlNode container = result.CreateElement("LAYOUT");
result.AppendChild(container);
foreach (Form form in Application.OpenForms)
{
ISaveLayout saveable = form as ISaveLayout;
if (null != saveable)
try
{
saveable.SaveLayout(container);
}
catch { }
}
if (null != SpecialSaveLayout)
try
{
SpecialSaveLayout(container);
}
catch { }
return result;
}
public void SaveAll(string fileName)
{
try
{
SaveAll().Save(fileName);
}
catch { }
}
// The output format of SaveOne and SaveAll are the same. The only difference
// is that SaveOne has only the one window in it. But it's still set up as a
// list of windows, just a list with only one entry. Presumably the main program
// will use a different file extension to seperate these two cases, and that
// will decide whether closeOldWindows is true or false in the call to Restore().
public void SaveOne(SaveLayout code, string fileName)
{
XmlDocument result = new XmlDocument();
XmlNode container = result.CreateElement("LAYOUT");
result.AppendChild(container);
code(container);
try
{
result.Save(fileName);
}
catch { }
}
public void SaveOne(ISaveLayout window, string fileName)
{
SaveOne(window.SaveLayout, fileName);
}
public void Restore(XmlNode all, bool closeOldWindows)
{
if (closeOldWindows)
{ // Create a copy of the list, because the list will be modified as soon
// as we close the first window.
List