programming4us
programming4us
WEBSITE

Application Patterns and Tips : Remember the Screen Location & Implement Undo Using Command Objects

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
1/12/2012 4:02:52 PM

Remember the Screen Location

Problem:You want your application to remember its location on the screen and restore to that location the next time the app runs.
Solution:Although this task is easy, you need to take into account that when you restore an application, what used to be on the screen before might not be on the screen anymore. For example, a user might rearrange a multiple-monitor scenario, or merely change the resolution of his screen to something smaller.

The screen location should be a user-specific setting. For the following example, two user settings were created in the standard Settings.settings file.

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
RestoreLocation();
}

private void RestoreLocation()
{
Point location = Properties.Settings.Default.FormLocation;
Size size = Properties.Settings.Default.FormSize;
//make sure location is on a monitor
bool isOnScreen = false;
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.Contains(location))
{
isOnScreen = true;
}
}
//if our window isn't visible, put it on primary monitor
if (!isOnScreen)
{
this.SetDesktopLocation(
Screen.PrimaryScreen.WorkingArea.Left,
Screen.PrimaryScreen.WorkingArea.Top);
}

//if too small, just reset to default
if (size.Width < 10 || size.Height < 10)
{
Size = new Size(300, 300);
}
}

private void SaveLocation()
{
//these are user settings I created in the
//Properties\Settings.settings file
Properties.Settings.Default.FormLocation = this.Location;
Properties.Settings.Default.FormSize = this.Size;
Properties.Settings.Default.Save();
}

protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);

SaveLocation();
}
}


Implement Undo Using Command Objects

Problem:You want to be able to undo commands in your application.
Solution:Most programs that let the user edit content have the ability to let the user undo the previous action. This section demonstrates a simple widget application that allows undo functionality (see Figure 1).
Figure 1. This simple application allows the user to undo moves, creations, and deletions.


The most popular way to implement this involves command objects that know how to undo themselves. Every possible action in the program is represented by a command object.

Note

Not everything the user can do in your application needs to be a command. For example, moving the cursor and changing the current selection aren’t usually considered actions. Generally, undoable commands should be those that change the user’s data.


Define the Command Interface and History Buffer

Here’s a possible interface:

interface ICommand
{
void Execute();
void Undo();
string Name { get; }
}

We also need a way to track all our commands in the order they were issued:

class CommandHistory
{
private Stack<ICommand> _stack = new Stack<ICommand>();

public bool CanUndo
{
get
{
return _stack.Count > 0;
}
}

public string MostRecentCommandName
{
get
{
if (CanUndo)
{
ICommand cmd = _stack.Peek();
return cmd.Name;
}
return string.Empty;
}
}

public void PushCommand(ICommand command)
{
_stack.Push(command);
}

public ICommand PopCommand()
{
return _stack.Pop();
}
}


Given these two things, the specific implementations of commands depends on the data structures of the application.

In this case, we have an IWidget interface defining all our objects:

interface IWidget
{
void Draw(Graphics graphics);
bool HitTest(Point point);
Point Location { get; set; }
Size Size { get; set; }
Rectangle BoundingBox { get; }
}

Define Command Functionality

One command we need is to be able to undo a drag/move operation. The command object needs only as much context to be able to do and undo the operation (in this case, the old location and the new location):

class MoveCommand : ICommand
{
private Point _originalLocation;
private Point _newLocation;
private IWidget _widget;

public MoveCommand(IWidget widget,
Point originalLocation,
Point newLocation)
{
this._widget = widget;
this._originalLocation = originalLocation;
this._newLocation = newLocation;
}

#region ICommand Members

public void Execute()
{
_widget.Location = _newLocation;
}

public void Undo()
{
_widget.Location = _originalLocation;
}

public string Name
{
get { return "Move widget"; }
}
#endregion
}


Here’s the CreateWidgetCommand object, which takes a different type of state:

class CreateWidgetCommand : ICommand
{
private ICollection<IWidget> _collection;
private IWidget _newWidget;

public CreateWidgetCommand(ICollection<IWidget> collection, IWidget newWidget)
{
_collection = collection;
_newWidget = newWidget;
}

#region ICommand Members

public void Execute()
{
_collection.Add(_newWidget);
}

public void Undo()
{
_collection.Remove(_newWidget);
}

public string Name
{
get { return "Create new widget"; }
}

#endregion
}


To use this functionality, you just have to create the command objects at the appropriate time. Here is the Form from the CommandUndo sample code. Look at the project in Visual Studio to see the full source.

public partial class Form1 : Form
{
private CommandHistory _history = new CommandHistory();
private List<IWidget> _widgets = new List<IWidget>();
private bool _isDragging = false;
private IWidget _dragWidget = null;
private Point _prevMousePt;
private Point _originalLocation;
private Point _newLocation;
public Form1()
{
InitializeComponent();

panelSurface.MouseDoubleClick += new MouseEventHandler(panelSurface_MouseDoubleClick);
panelSurface.Paint += new PaintEventHandler(panelSurface_Paint);
panelSurface.MouseMove +=
new MouseEventHandler(panelSurface_MouseMove);
panelSurface.MouseDown +=
new MouseEventHandler(panelSurface_MouseDown);
panelSurface.MouseUp +=
new MouseEventHandler(panelSurface_MouseUp);

editToolStripMenuItem.DropDownOpening += new EventHandler(editToolStripMenuItem_DropDownOpening);
undoToolStripMenuItem.Click +=
new EventHandler(undoToolStripMenuItem_Click);
}

void panelSurface_MouseDown(object sender, MouseEventArgs e)
{
IWidget widget = GetWidgetUnderPoint(e.Location);
if (widget != null)
{
_dragWidget = widget;
_isDragging = true;
_prevMousePt = e.Location;
_newLocation = _originalLocation = _dragWidget.Location;
}
}

void panelSurface_MouseMove(object sender, MouseEventArgs e)
{
if (!_isDragging)
{
IWidget widget = GetWidgetUnderPoint(e.Location);
if (widget != null)
{
panelSurface.Cursor = Cursors.SizeAll;
}
else
{
panelSurface.Cursor = Cursors.Default;
}
}
else if (_dragWidget != null)
{
Point offset = new Point(e.Location.X - _prevMousePt.X,
e.Location.Y - _prevMousePt.Y);

_prevMousePt = e.Location;

_newLocation.Offset(offset);
//update the widget temporarily as we move
//-- not a command in this case
//because we don't want to record every dragging operation
_dragWidget.Location = _newLocation;

Refresh();
}
}

void panelSurface_MouseUp(object sender, MouseEventArgs e)
{
if (_isDragging)
{
//now perform the command so that Undo restores to location
//before we started dragging
RunCommand(new MoveCommand(_dragWidget,
_originalLocation,
_newLocation));
}
_isDragging = false;
_dragWidget = null;
}

void panelSurface_MouseDoubleClick(object sender, MouseEventArgs e)
{
CreateNewWidget(e.Location);
}

private IWidget GetWidgetUnderPoint(Point point)
{
foreach (IWidget widget in _widgets)
{
if (widget.BoundingBox.Contains(point))
{
return widget;
}
}
return null;
}

void panelSurface_Paint(object sender, PaintEventArgs e)
{
foreach (IWidget widget in _widgets)
{
widget.Draw(e.Graphics);
}
}

//menu handling
void editToolStripMenuItem_DropDownOpening(object sender,
EventArgs e)
{
undoToolStripMenuItem.Enabled = _history.CanUndo;
if (_history.CanUndo)
{
undoToolStripMenuItem.Text = "&Undo "
+ _history.MostRecentCommandName;
}
else
{
undoToolStripMenuItem.Text = "&Undo";
}
}

void undoToolStripMenuItem_Click(object sender, EventArgs e)
{
UndoMostRecentCommand();
}

private void createToolStripMenuItem_Click(object sender,
EventArgs e)
{
CreateNewWidget(new Point(0, 0));
}

private void clearToolStripMenuItem_Click(object sender,
EventArgs e)
{
RunCommand(new DeleteAllWidgetsCommand(_widgets));
Refresh();
}
private void CreateNewWidget(Point point)
{
RunCommand(new CreateWidgetCommand(_widgets,
new Widget(point)));
Refresh();
}

private void RunCommand(ICommand command)
{
_history.PushCommand(command);
command.Execute();
}

private void UndoMostRecentCommand()
{
ICommand command = _history.PopCommand();
command.Undo();
Refresh();
}
}


Note

Although WPF has the notion of command objects already, they do not have the ability to undo themselves (which makes sense because undo is an application-dependent operation). The ideas in this section can be easily translated to WPF.

Other  
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
Video Sports
programming4us programming4us
programming4us
 
 
programming4us