public void CreateControls(FileDatabase database, DataEntryHandler dataEntryPropagator)
        {
            // Depending on how the user interacts with the file import process image set loading can be aborted after controls are generated and then
            // another image set loaded.  Any existing controls therefore need to be cleared.
            this.ControlGrid.Children.Clear();
            this.Controls.Clear();
            this.ControlsByDataLabel.Clear();

            DataEntryDateTime dateTimeControl = null;
            DataEntryUtcOffset utcOffsetControl = null;
            List<DataEntryControl> visibleControls = new List<DataEntryControl>();
            foreach (ControlRow control in database.Controls)
            {
                // no point in generating a control if it doesn't render in the UX
                if (control.Visible == false)
                {
                    continue;
                }

                if (control.Type == Constant.DatabaseColumn.DateTime)
                {
                    dateTimeControl = new DataEntryDateTime(control, this);
                    visibleControls.Add(dateTimeControl);
                }
                else if (control.Type == Constant.DatabaseColumn.File ||
                         control.Type == Constant.DatabaseColumn.RelativePath ||
                         control.Type == Constant.Control.Note)
                {
                    // standard controls rendering as notes aren't editable by the user
                    List<string> autocompletions = null;
                    bool readOnly = control.Type != Constant.Control.Note;
                    if (readOnly == false)
                    {
                        autocompletions = new List<string>(database.GetDistinctValuesInFileDataColumn(control.DataLabel));
                    }
                    DataEntryNote noteControl = new DataEntryNote(control, autocompletions, this);
                    noteControl.ContentReadOnly = readOnly;
                    visibleControls.Add(noteControl);
                }
                else if (control.Type == Constant.Control.Flag || control.Type == Constant.DatabaseColumn.DeleteFlag)
                {
                    DataEntryFlag flagControl = new DataEntryFlag(control, this);
                    visibleControls.Add(flagControl);
                }
                else if (control.Type == Constant.Control.Counter)
                {
                    DataEntryCounter counterControl = new DataEntryCounter(control, this);
                    visibleControls.Add(counterControl);
                }
                else if (control.Type == Constant.Control.FixedChoice || control.Type == Constant.DatabaseColumn.ImageQuality)
                {
                    DataEntryChoice choiceControl = new DataEntryChoice(control, this);
                    visibleControls.Add(choiceControl);
                }
                else if (control.Type == Constant.DatabaseColumn.UtcOffset)
                {
                    utcOffsetControl = new DataEntryUtcOffset(control, this);
                    visibleControls.Add(utcOffsetControl);
                }
                else
                {
                    Debug.Fail(String.Format("Unhandled control type {0}.", control.Type));
                    continue;
                }
            }

            if ((dateTimeControl != null) && (utcOffsetControl != null))
            {
                dateTimeControl.ShowUtcOffset();
                visibleControls.Remove(utcOffsetControl);
            }

            foreach (DataEntryControl control in visibleControls)
            {
                this.ControlGrid.Children.Add(control.Container);
                this.Controls.Add(control);
                this.ControlsByDataLabel.Add(control.DataLabel, control);
            }

            dataEntryPropagator.SetDataEntryCallbacks(this.ControlsByDataLabel);
        }
        private async Task CloseImageSetAsync()
        {
            if (this.IsFileDatabaseAvailable())
            {
                // persist image set properties if an image set has been opened
                if (this.dataHandler.FileDatabase.CurrentlySelectedFileCount > 0)
                {
                    // revert to custom selections to all 
                    if (this.dataHandler.FileDatabase.ImageSet.FileSelection == FileSelection.Custom)
                    {
                        this.dataHandler.FileDatabase.ImageSet.FileSelection = FileSelection.All;
                    }

                    // sync image set properties
                    if (this.MarkableCanvas != null)
                    {
                        this.dataHandler.FileDatabase.ImageSet.Options.SetFlag(ImageSetOptions.Magnifier, this.MarkableCanvas.MagnifyingGlassEnabled);
                    }

                    if (this.dataHandler.ImageCache != null && this.dataHandler.ImageCache.Current != null)
                    {
                        this.dataHandler.FileDatabase.ImageSet.MostRecentFileID = this.dataHandler.ImageCache.Current.ID;
                    }

                    // write image set properties to the database
                    this.dataHandler.FileDatabase.SyncImageSetToDatabase();

                    // ensure custom filter operator is synchronized in state for writing to user's registry
                    this.state.CustomSelectionTermCombiningOperator = this.dataHandler.FileDatabase.CustomSelection.TermCombiningOperator;
                }

                // discard the image set and reset UX for no open image set/no selected files
                this.dataHandler.Dispose();
                this.dataHandler = null;
                await this.EnableOrDisableMenusAndControlsAsync();
            }

            this.state.Reset();
            this.MenuEditRedo.IsEnabled = this.state.UndoRedoChain.CanRedo;
            this.MenuEditUndo.IsEnabled = this.state.UndoRedoChain.CanUndo;
        }
        /// <summary>
        /// Load the specified database template and then the associated file database.
        /// </summary>
        /// <param name="templateDatabasePath">Fully qualified path to the template database file.</param>
        /// <returns>true only if both the template and image database file are loaded (regardless of whether any images were loaded), false otherwise</returns>
        /// <remarks>This method doesn't particularly need to be public. But making it private imposes substantial complexity in invoking it via PrivateObject
        /// in unit tests.</remarks>
        public async Task<bool> TryOpenTemplateAndBeginLoadFoldersAsync(string templateDatabasePath)
        {
            // Try to create or open the template database
            TemplateDatabase templateDatabase;
            if (TemplateDatabase.TryCreateOrOpen(templateDatabasePath, out templateDatabase) == false)
            {
                // notify the user the template couldn't be loaded
                MessageBox messageBox = new MessageBox("Carnassial could not load the template.", this);
                messageBox.Message.Problem = "Carnassial could not load " + Path.GetFileName(templateDatabasePath) + Environment.NewLine;
                messageBox.Message.Reason = "\u2022 The template was created with the Timelapse template editor instead of the Carnassial editor." + Environment.NewLine;
                messageBox.Message.Reason = "\u2022 The template may be corrupted or somehow otherwise invalid.";
                messageBox.Message.Solution = String.Format("You may have to recreate the template, restore it from the {0} folder, or use another copy of it if you have one.", Constant.File.BackupFolder);
                messageBox.Message.Result = "Carnassial won't do anything.  You can try to select another template file.";
                messageBox.Message.Hint = "If the template can't be opened in a SQLite database editor the file is corrupt.";
                messageBox.Message.StatusImage = MessageBoxImage.Error;
                messageBox.ShowDialog();

                this.state.MostRecentImageSets.TryRemove(templateDatabasePath);
                this.MenuFileRecentImageSets_Refresh();
                return false;
            }

            // Try to get the file database file path
            // importImages will be true if it's a new image database file (meaning the user will be prompted import some images)
            string fileDatabaseFilePath;
            bool addFiles;
            if (this.TrySelectDatabaseFile(templateDatabasePath, out fileDatabaseFilePath, out addFiles) == false)
            {
                // No image database file was selected
                templateDatabase.Dispose();
                return false;
            }

            // Before running from an existing file database, check the controls in the template database are compatible with those
            // of the file database.
            FileDatabase fileDatabase;
            if (FileDatabase.TryCreateOrOpen(fileDatabaseFilePath, templateDatabase, this.state.OrderFilesByDateTime, this.state.CustomSelectionTermCombiningOperator, out fileDatabase) == false)
            {
                // notify the user the database couldn't be loaded
                MessageBox messageBox = new MessageBox("Carnassial could not load the database.", this);
                messageBox.Message.Problem = "Carnassial could not load " + Path.GetFileName(fileDatabaseFilePath) + Environment.NewLine;
                messageBox.Message.Reason = "\u2022 The database was created with Timelapse instead of Carnassial." + Environment.NewLine;
                messageBox.Message.Reason = "\u2022 The database may be corrupted or somehow otherwise invalid.";
                messageBox.Message.Solution = String.Format("You may have to recreate the database, restore it from the {0} folder, or use another copy of it if you have one.", Constant.File.BackupFolder);
                messageBox.Message.Result = "Carnassial won't do anything.  You can try to select another template or database file.";
                messageBox.Message.Hint = "If the database can't be opened in a SQLite database editor the file is corrupt.";
                messageBox.Message.StatusImage = MessageBoxImage.Error;
                messageBox.ShowDialog();
                return false;
            }
            templateDatabase.Dispose();

            if (fileDatabase.ControlSynchronizationIssues.Count > 0)
            {
                TemplateSynchronization templatesNotCompatibleDialog = new TemplateSynchronization(fileDatabase.ControlSynchronizationIssues, this);
                bool? result = templatesNotCompatibleDialog.ShowDialog();
                if (result == true)
                {
                    // user indicated not to update to the current template so exit.
                    Application.Current.Shutdown();
                    return false;
                }
                // user indicated to run with the stale copy of the template found in the image database
            }

            // valid template and file database loaded
            // generate and render the data entry controls regardless of whether there are actually any files in the file database.
            this.dataHandler = new DataEntryHandler(fileDatabase);
            this.DataEntryControls.CreateControls(fileDatabase, this.dataHandler);
            this.SetUserInterfaceCallbacks();

            this.MenuFileRecentImageSets_Refresh();
            this.state.MostRecentFileAddFolderPath = fileDatabase.FolderPath;
            this.state.MostRecentImageSets.SetMostRecent(templateDatabasePath);
            this.Title = Path.GetFileName(fileDatabase.FilePath) + " - " + Constant.MainWindowBaseTitle;

            // If this is a new file database, try to load files (if any) from the folder...  
            if (addFiles)
            {
                FolderLoad folderLoad = new FolderLoad();
                folderLoad.FolderPaths.Add(this.FolderPath);
                await this.TryBeginFolderLoadAsync(folderLoad);
            }

            await this.OnFolderLoadingCompleteAsync(false);
            return true;
        }
        public void CreateReuseControlsAndPropagate()
        {
            List<DatabaseExpectations> databaseExpectations = new List<DatabaseExpectations>()
            {
                new DatabaseExpectations()
                {
                    FileName = Constant.File.DefaultFileDatabaseFileName,
                    TemplateDatabaseFileName = TestConstant.File.DefaultTemplateDatabaseFileName,
                    ExpectedColumns = TestConstant.DefaultFileDataColumns,
                    ExpectedControls = TestConstant.DefaultFileDataColumns.Count - 6
                }
            };

            foreach (DatabaseExpectations databaseExpectation in databaseExpectations)
            {
                FileDatabase fileDatabase = this.CreateFileDatabase(databaseExpectation.TemplateDatabaseFileName, databaseExpectation.FileName);
                DataEntryHandler dataHandler = new DataEntryHandler(fileDatabase);

                DataEntryControls controls = new DataEntryControls();
                controls.CreateControls(fileDatabase, dataHandler);
                Assert.IsTrue(controls.ControlsByDataLabel.Count == databaseExpectation.ExpectedControls, "Expected {0} controls to be generated but {1} were.", databaseExpectation.ExpectedControls, controls.ControlsByDataLabel.Count);

                // check copies aren't possible when the image enumerator's not pointing to an image
                foreach (DataEntryControl control in controls.Controls)
                {
                    Assert.IsFalse(dataHandler.IsCopyForwardPossible(control));
                    Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control));
                }

                // check only copy forward is possible when enumerator's on first image
                List<FileExpectations> fileExpectations = this.PopulateDefaultDatabase(fileDatabase);
                Assert.IsTrue(dataHandler.ImageCache.MoveNext());

                List<DataEntryControl> copyableControls = controls.Controls.Where(control => control.Copyable).ToList();
                foreach (DataEntryControl control in copyableControls)
                {
                    Assert.IsTrue(dataHandler.IsCopyForwardPossible(control));
                    Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control));
                }

                List<DataEntryControl> notCopyableControls = controls.Controls.Where(control => control.Copyable == false).ToList();
                foreach (DataEntryControl control in notCopyableControls)
                {
                    Assert.IsFalse(dataHandler.IsCopyForwardPossible(control));
                    Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control));
                }

                // check only copy last is possible when enumerator's on last image
                // check also copy last is not possible if no previous instance of the field has been filled out
                while (dataHandler.ImageCache.CurrentRow < fileExpectations.Count - 1)
                {
                    Assert.IsTrue(dataHandler.ImageCache.MoveNext());
                }

                foreach (DataEntryControl control in copyableControls)
                {
                    Assert.IsFalse(dataHandler.IsCopyForwardPossible(control));
                    if (control.DataLabel == TestConstant.CarnivoreDatabaseColumn.Pelage ||
                        control.DataLabel == TestConstant.DefaultDatabaseColumn.ChoiceNotVisible ||
                        control.DataLabel == TestConstant.DefaultDatabaseColumn.ChoiceWithCustomDataLabel ||
                        control.DataLabel == TestConstant.DefaultDatabaseColumn.Choice3 ||
                        control.DataLabel == TestConstant.DefaultDatabaseColumn.Counter3 ||
                        control.DataLabel == TestConstant.DefaultDatabaseColumn.NoteWithCustomDataLabel)
                    {
                        Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control));
                    }
                    else
                    {
                        Assert.IsTrue(dataHandler.IsCopyFromLastNonEmptyValuePossible(control));
                    }
                }

                // propagation methods not covered due to requirement of UX interaction
                // dataHandler.CopyForward(control);
                // dataHandler.CopyFromLastValue(control);
                // dataHandler.CopyToAll(control);

                // verify roundtrip of fields subject to copy/paste and analysis assignment
                Assert.IsTrue(dataHandler.ImageCache.TryMoveToFile(0));
                ImageRow firstFile = fileDatabase.Files[0];
                FileExpectations firstFileExpectations = fileExpectations[0];

                Dictionary<string, object> firstFileValuesByDataLabel = firstFile.AsDictionary();
                Assert.IsTrue(firstFileValuesByDataLabel.Count == databaseExpectation.ExpectedColumns.Count);
                foreach (KeyValuePair<string, object> fileValue in firstFileValuesByDataLabel)
                {
                    DataEntryControl control;
                    if (controls.ControlsByDataLabel.TryGetValue(fileValue.Key, out control))
                    {
                        control.SetValue(firstFileValuesByDataLabel[fileValue.Key]);
                    }
                }

                TimeZoneInfo imageSetTimeZone = fileDatabase.ImageSet.GetTimeZone();
                firstFileExpectations.Verify(firstFile, imageSetTimeZone);

                // verify roundtrip of fields via display string
                foreach (KeyValuePair<string, DataEntryControl> control in controls.ControlsByDataLabel)
                {
                    string displayString = firstFile.GetValueDisplayString(control.Value);
                    control.Value.SetContentAndTooltip(displayString);
                }

                firstFileExpectations.Verify(firstFile, imageSetTimeZone);

                // verify availability of database strings
                foreach (string dataLabel in databaseExpectation.ExpectedColumns)
                {
                    string databaseString = firstFile.GetValueDatabaseString(dataLabel);
                }
            }
        }