I am developing a WPF designer where visual elements (rectangles, ellipses, ...) can be inserted, moved, resized and so on. Similar to a simple PowerPoint control.
All the actions should be undo-able, so all modifications to the drawing area's state are implemented as Operation
s. An operation looks like:
public abstract class Operation
{
protected Operation(string title)
{
Title = title;
}
internal abstract void Do();
internal abstract void Undo();
public string Title { get; }
}
There are other classes used in the code below:
- The
IFormDesigner
interface is the abstraction of the desinger surface - The
FormItem
class represents the model for one element on the designer and has (beside some other properties) anOrder
property of typeInt32
which determines the element's Z order.
I would like you to take a look on the arrange operation (SendToBack, SendBackward, BringToFront, BringForward). My first approach was, to implement a class ArrangeOperation
with an abstract method CalculateNewOrder
followed by 4 derived classes (SendToBack
, SendBackward
, BringToFront
, BringForward
) that implemented the abstract method. Because the method was called in contructor, I ran into ReSharper's virtual member call in constructor warning. Actually, the warning wasn't a problem in my case, but I want to avoid it anyway because it may become a problem if the code grows...
Finally I end up with a more functional solution:
internal class ArrangeOperation : Operation
{
private readonly IFormDesigner myDesigner;
private readonly Dictionary<FormItem, int> myOrdersBefore;
private readonly Dictionary<FormItem, int> myOrdersAfter;
private ArrangeOperation(IFormDesigner designer, string title, Action calculateNewOrder) : base(title)
{
myDesigner = designer;
myOrdersBefore = designer.FormElements.ToDictionary(e => e.Model, e => e.Model.Order);
calculateNewOrder();
myOrdersAfter = designer.FormElements.ToDictionary(e => e.Model, e => e.Model.Order);
}
internal override void Do()
{
ApplyOrders(myOrdersAfter);
myDesigner.InvalidateOrders();
}
internal override void Undo()
{
ApplyOrders(myOrdersBefore);
myDesigner.InvalidateOrders();
}
private static void ApplyOrders(Dictionary<FormItem, int> elementOrderDictionary)
{
foreach (var element in elementOrderDictionary)
element.Key.Order = element.Value;
}
public static Operation CreateSendToBack(IFormDesigner designer)
{
var formItems = designer.Selection.Select(i => i.Model).ToArray();
return new ArrangeOperation(designer, Resources.OperationSendToBack,
() =>
{
var otherItems = designer.FormElements
.Where(i => !formItems.Contains(i.Model))
.OrderBy(i => i.Model.Order)
.Select(f => f.Model)
.ToArray();
var newOrderdItems = formItems.OrderBy(i => i.Order).ToList();
newOrderdItems.AddRange(otherItems);
for (int i = 0; i < newOrderdItems.Count; i++)
newOrderdItems[i].Order = i + 1;
});
}
public static Operation CreateSendBackward(IFormDesigner designer)
{
var formItems = designer.Selection.Select(i => i.Model).ToArray();
return new ArrangeOperation(designer, Resources.OperationSendBackward,
() =>
{
var allFormItems = designer.FormElements.Select(f => f.Model).ToArray();
for (int i = 1; i < allFormItems.Length; i++)
{
if (formItems.Contains(allFormItems[i]))
allFormItems.Swap(i, i - 1);
}
for (int i = 0; i < allFormItems.Length; i++)
allFormItems[i].Order = i + 1;
});
}
public static Operation CreateBringToFront(IFormDesigner designer)
{
var formItems = designer.Selection.Select(i => i.Model).ToArray();
return new ArrangeOperation(designer, Resources.OperationBringToFront,
() =>
{
var newOrderdItems = designer.FormElements
.Where(i => !formItems.Contains(i.Model))
.OrderBy(i => i.Model.Order)
.Select(f => f.Model).ToList();
newOrderdItems.AddRange(formItems.OrderBy(i => i.Order));
for (int i = 0; i < newOrderdItems.Count; i++)
newOrderdItems[i].Order = i + 1;
});
}
public static Operation CreateBringForward(IFormDesigner designer)
{
var formItems = designer.Selection.Select(i => i.Model).ToArray();
return new ArrangeOperation(designer, Resources.OperationBringForward,
() =>
{
var allFormItems = designer.FormElements.Select(f => f.Model).ToArray();
for (int i = allFormItems.Length - 2; i >= 0; i--)
{
if (formItems.Contains(allFormItems[i]))
allFormItems.Swap(i, i + 1);
}
for (int i = 0; i < allFormItems.Length; i++)
allFormItems[i].Order = i + 1;
});
}
}
What do you think about the class design and the the logic for calculating the new order?
CreateBringToFront
action seems pointless. Are you trying to edit the original collection? \$\endgroup\$Order
property based on the order of the items. \$\endgroup\$