/// <summary>
        ///
        /// </summary>
        /// <param name="async">When true, runs the specified server method in a seperate thread.</param>
        /// <param name="postApplying">
        ///		If specified, this function is called after applying is complete. It is the
        ///		function's responsibility to call FloatingDialog.EndApplyChanges() in order to complete the apply cycle.
        /// </param>
        protected static void Dialog_ApplyingChanges <TableType, RowType>(TableType sourceTable, FloatingDialog dialog, MethodInfo saveMethod, CancelRoutedEventArgs e, object[] additionalArgs, bool async, Action postApplying)
            where TableType : DataTable, new()
            where RowType : DataRow
        {
            RowType   editVersion = dialog.Content as RowType;
            DataTable changes     = editVersion.Table.GetChanges();

            // No changes were made, skip the apply (but don't cancel)
            if (changes == null)
            {
                e.Skip = true;

                if (postApplying != null)
                {
                    postApplying();
                }
                else
                {
                    dialog.EndApplyChanges(e);
                }

                return;
            }

            // Remember data state
            DataRowState state = changes.Rows[0].RowState;

            // Will store updated data
            TableType savedVersion = null;

            object[] actualArgs = additionalArgs != null? new object[additionalArgs.Length + 1] : new object[1];
            actualArgs[0] = Oltp.Prepare <TableType>(changes);
            if (additionalArgs != null)
            {
                additionalArgs.CopyTo(actualArgs, 1);
            }

            // Save the changes to the DB
            Delegate[] delegates = new Delegate[] {
                (Action) delegate()
                {
                    using (OltpProxy proxy = new OltpProxy())
                    {
                        savedVersion = (TableType)saveMethod.Invoke(proxy.Service, actualArgs);
                    }
                },
                (Func <Exception, bool>) delegate(Exception ex)
                {
                    // Failed, so cancel and display a message
                    MessageBoxError("Error while updating.", ex);
                    e.Cancel = true;
                    return(false);
                },
                (Action) delegate()
                {
                    // Special case when adding a new row
                    if (state == DataRowState.Added)
                    {
                        // Import the saved row
                        sourceTable.ImportRow(savedVersion.Rows[0]);

                        // Even though nothing needs to be updated, mark it as added so AppliedChanges handled treats it properly
                        RowType newRow = sourceTable.Rows[sourceTable.Rows.Count - 1] as RowType;
                        newRow.SetAdded();

                        // Set as new target content
                        dialog.TargetContent = newRow;
                        dialog.Content       = Dialog_MakeEditVersion <TableType, RowType>(sourceTable, newRow);
                    }

                    // Activate the post applying action
                    if (postApplying != null)
                    {
                        postApplying();
                    }
                    else
                    {
                        dialog.EndApplyChanges(e);
                    }
                }
            };

            if (async)
            {
                App.CurrentPage.Window.AsyncOperation(delegates[0] as Action, delegates[1] as Func <Exception, bool>, delegates[2] as Action);
            }
            else
            {
                bool exception = false;
                try { (delegates[0] as Action).Invoke(); }
                catch (Exception ex) { (delegates[1] as Func <Exception, bool>).Invoke(ex); exception = true; }

                if (!exception)
                {
                    (delegates[2] as Action).Invoke();
                }

                if (postApplying != null)
                {
                    postApplying();
                }
                else
                {
                    dialog.EndApplyChanges(e);
                }
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="async">When true, runs the specified server method in a seperate thread.</param>
        /// <param name="postApplying">
        ///		If specified, this function is called after applying is complete. It is the
        ///		function's responsibility to call FloatingDialog.EndApplyChanges() in order to complete the apply cycle.
        /// </param>
        protected static void Dialog_ApplyingChanges <TableT, RowT>
        (
            TableT sourceTable,
            FloatingDialog dialog,
            MethodInfo saveMethod,
            CancelRoutedEventArgs e,
            object[] additionalArgs,
            bool async,
            Action postApplying
        )
            where TableT : DataTable, new()
            where RowT : DataRow
        {
            RowT      controlRow = dialog.Content as RowT;
            DataTable changes    = controlRow.Table.GetChanges();

            // No changes were made, skip the apply (but don't cancel)
            if (changes == null)
            {
                e.Skip = true;

                if (postApplying != null)
                {
                    postApplying();
                }
                else
                {
                    dialog.EndApplyChanges(e);
                }

                return;
            }

            if (dialog.IsBatch)
            {
                // Copy all target rows and apply the changed values to them
                TableT clonedTargetTable = new TableT();
                foreach (RowT row in (IEnumerable)dialog.TargetContent)
                {
                    clonedTargetTable.ImportRow(row);
                }
                clonedTargetTable.AcceptChanges();
                dialog.ApplyBindingsToItems(clonedTargetTable.Rows);
                // Get these changes
                changes = clonedTargetTable.GetChanges();
            }

            // No changes were made, skip the apply (but don't cancel)
            if (changes == null)
            {
                e.Skip = true;

                if (postApplying != null)
                {
                    postApplying();
                }
                else
                {
                    dialog.EndApplyChanges(e);
                }

                return;
            }

            // Remember data state
            DataRowState state = controlRow.RowState;

            // Will store updated data
            TableT savedVersion = null;

            // Create a typed version of the changes
            object[] actualArgs = additionalArgs != null? new object[additionalArgs.Length + 1] : new object[1];
            actualArgs[0] = Oltp.Prepare <TableT>(changes);
            if (additionalArgs != null)
            {
                additionalArgs.CopyTo(actualArgs, 1);
            }

            // Save the changes to the DB
            Delegate[] delegates = new Delegate[] {
                (Action) delegate()
                {
                    using (OltpProxy proxy = new OltpProxy())
                    {
                        savedVersion = (TableT)saveMethod.Invoke(proxy.Service, actualArgs);
                    }
                },
                (Func <Exception, bool>) delegate(Exception ex)
                {
                    // Failed, so cancel and display a message
                    MainWindow.MessageBoxError("Error while updating.\n\nChanges may have been saved, if re-saving doesn't work please refresh the page.", ex);
                    e.Cancel = true;
                    return(false);
                },
                (Action) delegate()
                {
                    // Special case when adding a new row
                    if (!dialog.IsBatch && state == DataRowState.Added)
                    {
                        // Import the saved row
                        sourceTable.ImportRow(savedVersion.Rows[0]);

                        // Even though nothing needs to be updated, mark it as added so AppliedChanges handled treats it properly
                        RowT newRow = sourceTable.Rows[sourceTable.Rows.Count - 1] as RowT;
                        newRow.SetAdded();

                        // Set as new target content
                        dialog.TargetContent = newRow;
                        dialog.Content       = Dialog_MakeEditVersion <TableT, RowT>(newRow);
                    }

                    // Activate the post applying action
                    if (postApplying != null)
                    {
                        postApplying();
                    }
                    else
                    {
                        dialog.EndApplyChanges(e);
                    }
                }
            };

            if (async)
            {
                App.CurrentPage.Window.AsyncOperation(
                    delegates[0] as Action,                                     // out-of-ui action
                    delegates[1] as Func <Exception, bool>,                     // exception handler
                    delegates[2] as Action                                      // in-ui action
                    );
            }
            else
            {
                bool exception = false;
                try
                {
                    // out-of-ui action, done in ui because async is not required
                    (delegates[0] as Action).Invoke();
                }
                catch (Exception ex)
                {
                    // exception handler
                    (delegates[1] as Func <Exception, bool>).Invoke(ex);
                    exception = true;
                }

                // in-ui action
                if (!exception)
                {
                    (delegates[2] as Action).Invoke();
                }

                if (postApplying != null)
                {
                    postApplying();
                }
                else
                {
                    dialog.EndApplyChanges(e);
                }
            }
        }