/*=========================*/
        #endregion

        #region Dialog-related Methods
        /*=========================*/

        /// <summary>
        ///
        /// </summary>
        protected void Dialog_Open <TableT, RowT>
        (
            FloatingDialog dialog,
            ListTable listTable,
            ListViewItem clickedItem,
            bool allowBatch,
            Func <RowT, bool, string> dialogTitle,
            Func <RowT, bool, string> dialogTooltip,
            Func <DataColumn, object> batchFlatten
        )
            where TableT : DataTable, new()
            where RowT : DataRow
        {
            // When a single item is clicked, select it
            if (clickedItem != null && clickedItem.DataContext != null && !listTable.ListView.SelectedItems.Contains(clickedItem.DataContext))
            {
                listTable.ListView.SelectedItems.Clear();
                clickedItem.IsSelected = true;
            }

            bool batch = listTable.ListView.SelectedItems.Count > 1;

            if (!allowBatch)
            {
                MainWindow.MessageBoxError("You cannot edit more than one item at a time.", null);
                return;
            }

            RowT[] targetRows = new RowT[listTable.ListView.SelectedItems.Count];
            try { listTable.ListView.SelectedItems.CopyTo(targetRows, 0); }
            catch (InvalidCastException)
            {
                MainWindow.MessageBoxError("To edit multiple items, select items of the same type.", null);
                return;
            }

            RowT controlRow = Dialog_MakeEditVersion <TableT, RowT>(targetRows, batchFlatten);

            // Show the dialog
            dialog.Title = dialogTitle != null?dialogTitle(controlRow, batch) : "Editing " + typeof(RowT).Name;

            dialog.TitleTooltip = dialogTooltip != null?dialogTooltip(controlRow, batch) : null;

            dialog.BeginEdit(controlRow, targetRows.Length > 1 ? (object)targetRows : (object)targetRows[0]);
        }
        /// <summary>
        ///
        /// </summary>
        protected static void Dialog_AppliedChanges <RowT>(FloatingDialog dialog, string editingDialogTitle, ListView hostListView, int minPosition, CancelRoutedEventArgs e)
            where RowT : DataRow
        {
            // All went well, so just accept changes
            RowT[] targetRows = dialog.TargetContent is RowT ? new RowT[] { dialog.TargetContent as RowT } : dialog.TargetContent as RowT[];
            RowT   finalRow   = dialog.IsBatch ? null : targetRows[0];

            targetRows[0].Table.AcceptChanges();
            (dialog.Content as RowT).AcceptChanges();

            // Add to the table
            if (!dialog.IsBatch && finalRow.RowState == DataRowState.Added)
            {
                ObservableCollection <DataRow> items = hostListView.ItemsSource as ObservableCollection <DataRow>;

                // -2 because we already added the row to the table. We want the position of the last available
                int newIndex = finalRow.Table.Rows.Count > 1 ?
                               items.IndexOf(finalRow.Table.Rows[finalRow.Table.Rows.Count - 2]) + 1 :
                               minPosition;

                if (newIndex >= 0)
                {
                    // Insert a new row to the items only if a new position has been found
                    items.Insert(newIndex, finalRow);
                    hostListView.SelectedIndex = newIndex;
                    hostListView.ScrollIntoView(finalRow);
                }
            }

            foreach (RowT row in targetRows)
            {
                if (row is IPropertyChangeNotifier)
                {
                    (row as IPropertyChangeNotifier).OnAllPropertiesChanged();
                }
            }

            if (!dialog.IsBatch)
            {
                dialog.Title = editingDialogTitle;
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="dialog"></param>
        /// <returns></returns>
        protected static bool MessageBoxPromptForCancel(FloatingDialog dialog)
        {
            DataRow row = dialog.Content as DataRow;

            if (row == null)
            {
                return(false);
            }

            if (row.Table.GetChanges() == null)
            {
                return(false);
            }

            MessageBoxResult result = MessageBox.Show(
                "Discard changes?",
                "Confirm",
                MessageBoxButton.OKCancel,
                MessageBoxImage.Warning,
                MessageBoxResult.Cancel);

            return(result == MessageBoxResult.Cancel);
        }
 /// <summary>
 ///
 /// </summary>
 protected static void Dialog_AppliedChanges <RowType>(FloatingDialog dialog, string editingDialogTitle, ListView hostListView, CancelRoutedEventArgs e)
     where RowType : DataRow
 {
     Dialog_AppliedChanges <RowType>(dialog, editingDialogTitle, hostListView, 0, 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 <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);
                }
            }
        }
 protected static void Dialog_ApplyingChanges <TableType, RowType>(TableType sourceTable, FloatingDialog dialog, MethodInfo saveMethod, CancelRoutedEventArgs e, object[] additionalArgs)
     where TableType : DataTable, new()
     where RowType : DataRow
 {
     Dialog_ApplyingChanges <TableType, RowType>(sourceTable, dialog, saveMethod, e, additionalArgs, false, null);
 }
        /// <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);
                }
            }
        }
 protected static void Dialog_ApplyingChanges <TableT, RowT>(TableT sourceTable, FloatingDialog dialog, MethodInfo saveMethod, CancelRoutedEventArgs e)
     where TableT : DataTable, new()
     where RowT : DataRow
 {
     Dialog_ApplyingChanges <TableT, RowT>(sourceTable, dialog, saveMethod, e, null, false, null);
 }