#region License Information
/* HeuristicLab
* Copyright (C) 2002-2013 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO.Compression;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
using HeuristicLab.DataImporter.Data;
using HeuristicLab.DataImporter.Data.CommandBase;
using HeuristicLab.DataImporter.Data.Model;
using HeuristicLab.DataImporter.Data.View;
using HeuristicLab.DataImporter.DataProcessor.Command;
using HeuristicLab.DataImporter.DbExplorer.Interfaces;
using HeuristicLab.Persistence.Default.Xml;
using HeuristicLab.PluginInfrastructure;
using HeuristicLab.Problems.DataAnalysis;
namespace HeuristicLab.DataImporter.DataProcessor {
public partial class DataProcessorView : Form {
private DataSetView dataSetView;
private BackgroundWorker backgroundWorker;
private string lastSavedFile;
private ICommand lastSavedCommand;
private ProgressDialog progressDlg;
private DataProcessorView() {
InitializeComponent();
this.HScroll = false;
this.HorizontalScroll.Visible = false;
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
public DataProcessorView(DataProcessor dataProcessor)
: this() {
this.dataProcessor = dataProcessor;
this.dataProcessor.CommandChain.Changed += this.CommandChainChanged;
this.dataSetView = new DataSetView(this.dataProcessor.DataSet, this.dataProcessor.CommandChain);
this.dataSetView.StateChanged += new EventHandler(DataSetStateChanged);
this.dataSetView.Dock = DockStyle.Fill;
this.dataSetView.ColumnGroupMaxWidth = this.splitContainer1.Panel2.Width;
this.splitContainer1.Panel2.Controls.Add(this.dataSetView);
this.splitContainer1.Panel2.Resize += new EventHandler(Panel2_Resize);
FillItemList();
IDbExplorer[] explorers = ApplicationManager.Manager.GetInstances().ToArray();
ToolStripItem item;
foreach (IDbExplorer exp in explorers) {
item = new ToolStripMenuItem();
item.Text = exp.MenuItemDescription;
item.Tag = exp;
item.Click += new EventHandler(setGenericDBConnectionToolStripMenuItem_Click);
databaseToolStripMenuItem.DropDownItems.Add(item);
}
}
private DataProcessor dataProcessor;
public DataProcessor DataProcessor {
get { return this.dataProcessor; }
}
private bool UndoEnabled {
get { return this.undoToolStripButton.Enabled; }
set { this.undoToolStripButton.Enabled = value; this.undoToolStripMenuItem.Enabled = value; }
}
private bool RedoEnabled {
get { return this.redoToolStripButton.Enabled; }
set { this.redoToolStripButton.Enabled = value; this.redoToolStripMenuItem.Enabled = value; }
}
private bool SaveCommandChainEnabled {
get { return this.saveCommandChainToolStripButton.Enabled; }
set { this.saveCommandChainToolStripButton.Enabled = value; }
}
#region CommandList
private void listCommands_MouseDoubleClick(object sender, MouseEventArgs e) {
ListViewItem item = listCommands.HitTest(e.Location).Item;
if (item != null) {
ICommandGenerator generator = (ICommandGenerator)listCommands.FocusedItem;
if (generator.Enabled(dataSetView.State)) {
ICommand cmd = ((ICommandGenerator)generator).GenerateCommand(dataProcessor.DataSet);
if (cmd != null) {
try {
dataProcessor.CommandChain.Add(cmd);
}
catch (CommandExecutionException cee) {
MessageBox.Show(cee.Message, cee.Command.Description);
}
}
}
}
item.Selected = false;
}
private void FillItemList() {
//sort after ViewableCommandInfoAttribute
IEnumerable commands = ApplicationManager.Manager.GetTypes(typeof(ICommand)).Select(t => new {
Command = t,
Attribute = (ViewableCommandInfoAttribute)Attribute.GetCustomAttribute(t, typeof(ViewableCommandInfoAttribute))
})
.Where(t => t.Attribute != null && !t.Command.IsAbstract)
.OrderBy(t => t.Attribute, new AttributeComparer())
.Select(t => t.Command);
ListViewItem item = null;
bool addGroup;
foreach (Type t in commands) {
item = (ListViewItem)Activator.CreateInstance(typeof(CommandListViewItem<>).MakeGenericType(t));
addGroup = true;
foreach (ListViewGroup grp in listCommands.Groups)
if (grp.Name == item.Group.Name) {
item.Group = grp;
addGroup = false;
break;
}
if (addGroup) {
listCommands.Groups.Add(item.Group);
}
listCommands.Items.Add(item);
}
UpdateListViewItems();
}
public void DataSetStateChanged(object sender, EventArgs e) {
UpdateListViewItems();
}
private void UpdateListViewItems() {
foreach (ICommandGenerator generator in listCommands.Items) {
if (generator.Enabled(dataSetView.State))
((ListViewItem)generator).ForeColor = Color.Black;
else
((ListViewItem)generator).ForeColor = Color.LightGray;
}
}
#endregion
private void CommandChainChanged(object sender, EventArgs e) {
#region undo & redo updates
this.UndoEnabled = !this.dataProcessor.CommandChain.FirstCommandReached;
this.RedoEnabled = !this.dataProcessor.CommandChain.LastCommandReached;
this.SaveCommandChainEnabled = !this.dataProcessor.CommandChain.FirstCommandReached;
this.undoToolStripButton.ToolTipText = this.dataProcessor.CommandChain.PreviousCommandDescription;
this.redoToolStripButton.ToolTipText = this.dataProcessor.CommandChain.NextCommandDescription;
//needed to update tooltip if it is currently being shown - taken from
//http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=167320
System.Reflection.FieldInfo f = this.toolStrip.GetType().GetField("currentlyActiveTooltipItem", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (object.ReferenceEquals(f.GetValue(this.toolStrip), this.undoToolStripButton)) {
// Need to set currentlyActiveTooltipItem to null, so the call to UpdateToolTip actually does the update
f.SetValue(this.toolStrip, null);
System.Reflection.MethodInfo m = this.toolStrip.GetType().GetMethod("UpdateToolTip", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
m.Invoke(this.toolStrip, new object[] { this.undoToolStripButton });
}
f = this.toolStrip.GetType().GetField("currentlyActiveTooltipItem", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (object.ReferenceEquals(f.GetValue(this.toolStrip), this.redoToolStripButton)) {
// Need to set currentlyActiveTooltipItem to null, so the call to UpdateToolTip actually does the update
f.SetValue(this.toolStrip, null);
System.Reflection.MethodInfo m = this.toolStrip.GetType().GetMethod("UpdateToolTip", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
m.Invoke(this.toolStrip, new object[] { this.redoToolStripButton });
}
#endregion
#region macro recording
Bitmap image;
string toolTipText;
if (dataProcessor.CommandChain.IsMacroRecording) {
image = Properties.Resources.StopHS;
toolTipText = "Stop Macro recording";
} else {
image = Properties.Resources.RecordHS;
toolTipText = "Start Macro recording";
}
recordMacroToolStripButton.Image = image;
recordMacroToolStripButton.ToolTipText = toolTipText;
f = this.toolStrip.GetType().GetField("currentlyActiveTooltipItem", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (object.ReferenceEquals(f.GetValue(this.toolStrip), this.recordMacroToolStripButton)) {
// Need to set currentlyActiveTooltipItem to null, so the call to UpdateToolTip actually does the update
f.SetValue(this.toolStrip, null);
System.Reflection.MethodInfo m = this.toolStrip.GetType().GetMethod("UpdateToolTip", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
m.Invoke(this.toolStrip, new object[] { this.recordMacroToolStripButton });
}
#endregion
if (!string.IsNullOrEmpty(lastSavedFile)) {
if (lastSavedCommand != this.dataProcessor.CommandChain.LastExecutedCommand && !lastSavedFile.Contains('*'))
this.Text = "Data Importer - " + lastSavedFile + "*";
else
this.Text = "Data Importer - " + lastSavedFile;
}
}
private void chbReorderColumns_CheckedChanged(object sender, EventArgs e) {
this.dataSetView.AllowReorderColumns = chbReorder.CheckBoxControl.Checked;
if (chbReorder.CheckBoxControl.Checked) {
foreach (ListViewItem item in listCommands.Items)
item.ForeColor = Color.LightGray;
listCommands.Enabled = false;
} else {
Dictionary dictionary = new Dictionary();
int[] displayIndices;
for (int i = this.dataSetView.Controls.Count - 1; i >= 0; i--) {
displayIndices = ((ColumnGroupView)this.dataSetView.Controls[i]).DisplayIndexes;
//check if displayIndices are ordered, otherwise the array is added to the list
for (int j = 0; j < displayIndices.Length; j++) {
if (j != 0 && ((displayIndices[j] - displayIndices[j - 1]) != 1)) {
ColumnGroup columnGroup = this.dataSetView.DataSet.ColumnGroups.ElementAt(this.dataSetView.Controls.Count - 1 - i);
dictionary[columnGroup.Name] = displayIndices;
break;
}
}
}
if (dictionary.Count != 0)
this.dataProcessor.CommandChain.Add(new ReorderColumnsCommand(this.dataProcessor.DataSet, dictionary));
listCommands.Enabled = true;
UpdateListViewItems();
}
}
private void UpdateDataSetView() {
this.dataSetView.Dispose();
this.dataSetView = new DataSetView(this.dataProcessor.DataSet, this.dataProcessor.CommandChain);
this.dataSetView.StateChanged += new EventHandler(DataSetStateChanged);
UpdateListViewItems();
this.dataSetView.Dock = DockStyle.Fill;
this.splitContainer1.Panel2.Controls.Clear();
this.splitContainer1.Panel2.Controls.Add(this.dataSetView);
this.dataSetView.ColumnGroupMaxWidth = this.splitContainer1.Panel2.Width;
this.dataProcessor.CommandChain.Changed += this.CommandChainChanged;
GC.Collect();
}
private void Panel2_Resize(object sender, EventArgs e) {
this.dataSetView.ColumnGroupMaxWidth = this.splitContainer1.Panel2.Width;
}
#region BackgroundWorker
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
CommandChain temp = (CommandChain)e.Argument;
int i = 0;
int max = temp.CommandsCount;
foreach (DataSetCommandBase cmd in temp) {
//update description
backgroundWorker.ReportProgress(i * 100 / max, cmd.Description);
cmd.DataSet = dataProcessor.DataSet;
if (cmd is DatabaseCommandBase)
((DatabaseCommandBase)cmd).DbExplorer = this.dataProcessor.DatabaseExplorer;
this.dataProcessor.CommandChain.Add(cmd, false);
i++;
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
progressDlg.ProgressBar.Value = 100;
progressDlg.Refresh();
this.Enabled = true;
UpdateDataSetView();
progressDlg.Close();
if (e.Error != null) {
string message = e.Error.Message;
if (e.Error is CommandExecutionException)
message += Environment.NewLine + "Command: " +
((CommandExecutionException)e.Error).Command.GetType().ToString();
MessageBox.Show(message, "Error occured during loading of command chain");
}
this.dataProcessor.CommandChain.FireChanged();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
progressDlg.Label.Text = (string)e.UserState;
progressDlg.ProgressBar.Value = e.ProgressPercentage;
progressDlg.Refresh();
}
#endregion
#region GUI actions
private void NewFile() {
if (!CheckUnsavedChanges())
return;
this.DataProcessor.Reset();
this.lastSavedFile = "";
this.Text = "Data Importer";
UpdateDataSetView();
}
private void Open() {
if (!CheckUnsavedChanges())
return;
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "DataImporter file (*.dhl)|*.dhl|All files (*.*)|*.*";
dlg.RestoreDirectory = true;
if (dlg.ShowDialog() == DialogResult.OK) {
try {
DataProcessor dp = (DataProcessor)XmlParser.Deserialize(dlg.FileName);
dp.DatabaseExplorer = this.dataProcessor.DatabaseExplorer;
this.lastSavedFile = dlg.FileName;
this.Text = "Data Importer - " + lastSavedFile;
this.dataProcessor.Reset();
this.dataProcessor = dp;
}
catch (XmlException e) {
MessageBox.Show(e.Message, "Error occured during file open.");
}
finally {
this.lastSavedCommand = null;
UpdateDataSetView();
this.dataProcessor.CommandChain.FireChanged();
}
}
}
private bool CheckUnsavedChanges() {
if (this.lastSavedCommand != this.dataProcessor.CommandChain.LastExecutedCommand) {
DialogResult result = new SaveDialog().ShowDialog(this);
if (result == DialogResult.Yes)
return this.Save();
else if (result == DialogResult.Cancel)
return false;
}
return true;
}
private bool Save() {
if (string.IsNullOrEmpty(this.lastSavedFile))
return this.SaveAs();
this.Save(dataProcessor, this.lastSavedFile);
return true;
}
private void Save(object serialize, string filename) {
try {
this.Cursor = Cursors.WaitCursor;
XmlGenerator.Serialize(serialize, filename, CompressionLevel.Optimal);
this.Cursor = Cursors.Default;
this.Text = "Data Importer - " + lastSavedFile;
this.lastSavedCommand = this.dataProcessor.CommandChain.LastExecutedCommand;
}
finally {
this.Cursor = Cursors.Default;
}
}
private bool SaveAs() {
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.AddExtension = true;
saveFileDialog.RestoreDirectory = true;
saveFileDialog.Filter = "Regression Problem file (*.hl)|*.hl|Classification Problem file (*.hl)|*.hl|DataImporter file (*.dhl)|*.dhl|All files (*.*)|*.*";
saveFileDialog.DefaultExt = "dhl";
saveFileDialog.FilterIndex = 3;
if (saveFileDialog.ShowDialog() == DialogResult.OK) {
this.lastSavedFile = saveFileDialog.FileName;
if (saveFileDialog.FilterIndex <= 2) {
Dataset dataset = ConvertDataset(dataProcessor.DataSet);
DataAnalysisProblemData problemData = null;
if (saveFileDialog.FilterIndex == 1) {
problemData = new RegressionProblemData(dataset, dataset.DoubleVariables, dataset.DoubleVariables.First());
} else if (saveFileDialog.FilterIndex == 2) {
problemData = new ClassificationProblemData(dataset, dataset.DoubleVariables, dataset.DoubleVariables.First());
} else {
throw new ArgumentException("File extension doesn't exist");
}
Save(problemData, lastSavedFile);
} else {
Save(dataProcessor, lastSavedFile);
}
return true;
}
return false;
}
private Dataset ConvertDataset(DataSet dataSet) {
ColumnGroup cg;
if (dataSet.ActiveColumnGroups.Any()) {
cg = dataSet.ActiveColumnGroups.First();
} else if (dataSet.ColumnGroups.Any()) {
cg = dataSet.ColumnGroups.First();
} else {
throw new ArgumentException("There exists no ColumnGroup which could be saved!");
}
List values = new List();
foreach (var column in cg.Columns) {
if (column.DataType == typeof(double?)) {
values.Add(column.ValuesEnumerable.Cast().Select(x => x ?? double.NaN).ToList());
} else if (column.DataType == typeof(string)) {
values.Add(column.ValuesEnumerable.Cast().Select(x => x ?? string.Empty).ToList());
} else if (column.DataType == typeof(DateTime?)) {
values.Add(column.ValuesEnumerable.Cast().Select(x => x ?? default(DateTime)).ToList());
} else {
throw new ArgumentException("The variable values must be of type List, List or List");
}
}
IEnumerable variableNames = cg.Columns.Select(x => x.Name);
return new Dataset(variableNames, values);
}
private void RecordMacro() {
if (dataProcessor.CommandChain.IsMacroRecording)
this.SaveCommandChain();
this.dataProcessor.CommandChain.ToggleMacroRecording();
}
private void OpenCommandChain() {
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "DataImporter Command file (*.dhlcmd)|*.dhlcmd|All files (*.*)|*.*";
dlg.RestoreDirectory = true;
if (dlg.ShowDialog() == DialogResult.OK) {
this.dataSetView.Dispose();
this.Cursor = Cursors.WaitCursor;
this.Enabled = false;
try {
CommandChain temp = (CommandChain)XmlParser.Deserialize(dlg.FileName);
progressDlg = new ProgressDialog();
progressDlg.DesktopLocation = new Point(this.Location.X + this.Width / 2 - progressDlg.Width / 2, this.Location.Y + this.Height / 2 - progressDlg.Height / 2);
backgroundWorker.RunWorkerAsync(temp);
progressDlg.ShowDialog();
this.Refresh();
}
finally {
this.Enabled = true;
this.Cursor = Cursors.Default;
}
}
}
private void SaveCommandChain() {
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.AddExtension = true;
saveFileDialog.RestoreDirectory = true;
saveFileDialog.DefaultExt = "dhlcmd";
saveFileDialog.Filter = "DataImporter Command file (*.dhlcmd)|*.dhlcmd|All files (*.*)|*.*";
if (saveFileDialog.ShowDialog() == DialogResult.OK) {
try {
this.Cursor = Cursors.WaitCursor;
XmlGenerator.Serialize(this.dataProcessor.CommandChain, saveFileDialog.FileName, CompressionLevel.Optimal);
}
finally {
this.Cursor = Cursors.Default;
}
}
}
private void Export() {
ExportDialog exportDlg = new ExportDialog(this.dataProcessor.DataSet);
exportDlg.StartPosition = FormStartPosition.CenterParent;
exportDlg.ShowDialog(this);
}
private void Import() {
ImportDialog importDlg = new ImportDialog(this.dataProcessor.DataSet, this.dataProcessor.CommandChain);
importDlg.StartPosition = FormStartPosition.CenterParent;
importDlg.ShowDialog(this);
}
private void Undo() {
this.dataProcessor.CommandChain.Undo();
this.Focus();
}
private void Redo() {
this.dataProcessor.CommandChain.Redo();
this.Focus();
}
private void AddTables() {
if (dataProcessor.DatabaseExplorer == null || !dataProcessor.DatabaseExplorer.IsConnected) {
MessageBox.Show("Not connected to database!");
return;
}
DbTablesView tablesForm = new DbTablesView();
tablesForm.DbExplorer = dataProcessor.DatabaseExplorer;
tablesForm.SetTables(dataProcessor.DatabaseExplorer.AllTables);
tablesForm.StartPosition = FormStartPosition.CenterParent;
if (tablesForm.ShowDialog(this) == DialogResult.OK) {
progressDlg = new ProgressDialog();
progressDlg.Label.Text = "Loading data from database";
progressDlg.Show(this);
progressDlg.DesktopLocation = new Point(this.Location.X + this.Width / 2 - progressDlg.Width / 2, this.Location.Y + this.Height / 2 - progressDlg.Height / 2);
this.Refresh();
this.dataProcessor.CommandChain.Add(new LoadColumnGroupsFromDBCommand(this.dataProcessor.DataSet, this.dataProcessor.DatabaseExplorer,
tablesForm.SelectedTables));
progressDlg.Close();
}
}
private void DatabaseExplorer_NewRowLoaded(object sender, ProgressChangedEventArgs e) {
progressDlg.ProgressBar.Value = e.ProgressPercentage;
progressDlg.Label.Text = (string)e.UserState;
progressDlg.Refresh();
}
private void AddTablesWithSQLCommand() {
CommandViewDialog dlg = new CommandViewDialog();
dlg.StartPosition = FormStartPosition.CenterParent;
SqlCommandView cmdView = new SqlCommandView();
dlg.Panel.Controls.Add(cmdView);
dlg.Panel.Size = cmdView.Size;
dlg.ShowDialog(this);
if (dlg.DialogResult == DialogResult.OK) {
this.dataProcessor.CommandChain.Add(new LoadColumnGroupWithSqlStringFromDBCommand(this.dataProcessor.DataSet, this.dataProcessor.DatabaseExplorer, cmdView.SqlCommand));
}
}
private void SetDbConnection() {
dataProcessor.DatabaseExplorer.ShowDbConnectionWizard();
if (dataProcessor.DatabaseExplorer.IsConnected) {
this.lblConnectionStatus.Text = "Successfully connected to database!";
this.lblConnectionStatus.BackColor = Color.LightGreen;
} else {
this.lblConnectionStatus.Text = "Not connected to database!";
this.lblConnectionStatus.BackColor = Color.FromArgb(255, 128, 128); //light red
}
}
#endregion
#region GUI ClickEvents - menuitems, toolbarbuttons
private void DataProcessorView_FormClosing(object sender, FormClosingEventArgs e) {
if (!CheckUnsavedChanges())
e.Cancel = true;
}
#region MenuItems
private void newToolStripMenuItem_Click(object sender, EventArgs e) {
this.NewFile();
}
private void openToolStripMenuItem_Click(object sender, EventArgs e) {
this.Open();
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
this.Save();
}
private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) {
this.SaveAs();
}
private void importToolStripMenuItem_Click(object sender, EventArgs e) {
this.Import();
}
private void exportToolStripMenuItem_Click(object sender, EventArgs e) {
this.Export();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e) {
this.Close();
}
private void undoToolStripMenuItem_Click(object sender, EventArgs e) {
this.Undo();
}
private void redoToolStripMenuItem_Click(object sender, EventArgs e) {
this.Redo();
}
private void addTablesToolStripMenuItem_Click(object sender, EventArgs e) {
this.AddTables();
}
private void addTablesWithSqlStringToolStripMenuItem_Click(object sender, EventArgs e) {
this.AddTablesWithSQLCommand();
}
private void setGenericDBConnectionToolStripMenuItem_Click(object sender, EventArgs e) {
this.dataProcessor.DatabaseExplorer = (IDbExplorer)((ToolStripMenuItem)sender).Tag;
this.dataProcessor.DatabaseExplorer.NewRowLoaded += new ProgressChangedEventHandler(DatabaseExplorer_NewRowLoaded);
this.SetDbConnection();
}
#endregion
#region ToolbarButtons
private void newToolStripButton_Click(object sender, EventArgs e) {
this.NewFile();
}
private void openToolStripButton_Click(object sender, EventArgs e) {
this.Open();
}
private void saveToolStripButton_Click(object sender, EventArgs e) {
this.Save();
}
private void recordMacroToolStripButton_Click(object sender, EventArgs e) {
this.RecordMacro();
}
private void openCommandChainToolStripButton_Click(object sender, EventArgs e) {
this.OpenCommandChain();
}
private void saveCommandChaingToolStripButton_Click(object sender, EventArgs e) {
this.SaveCommandChain();
}
private void undoToolStripButton_Click(object sender, EventArgs e) {
this.Undo();
}
private void redoToolStripButton_Click(object sender, EventArgs e) {
this.Redo();
}
#endregion
#endregion
private class AttributeComparer : IComparer {
public int Compare(ViewableCommandInfoAttribute x, ViewableCommandInfoAttribute y) {
int cmpResult = x.GroupName.CompareTo(y.GroupName);
if (cmpResult != 0)
return cmpResult;
cmpResult = x.Position.CompareTo(y.Position);
if (cmpResult != 0)
return cmpResult;
return x.Name.CompareTo(y.Name);
}
}
}
}