// All bitmap laoding eventually invokes this static function public static BitmapSource GetBitmapFromImageFile(string filePath, Nullable <int> desiredWidthOrHeight, ImageDisplayIntentEnum displayIntent, ImageDimensionEnum imageDimension, out bool isCorruptOrMissing) { isCorruptOrMissing = true; // BitmapCacheOption.None is significantly faster than other options. // However, it locks the file as it is being accessed (rather than a memory copy being created when using a cache) // This means we cannot do any file operations on it (such as deleting the currently displayed image) as it will produce an access violation. // This is ok for TransientLoading, which just temporarily displays the image BitmapCacheOption bitmapCacheOption = (displayIntent == ImageDisplayIntentEnum.Ephemeral) ? BitmapCacheOption.None : BitmapCacheOption.OnLoad; if (!System.IO.File.Exists(filePath)) { return(Constant.ImageValues.FileNoLongerAvailable.Value); } try { // Exception workarounds to consider: see http://faithlife.codes/blog/2010/07/exceptions_thrown_by_bitmapimage_and_bitmapframe/ if (desiredWidthOrHeight.HasValue == false) { // returns the full size bitmap BitmapFrame frame = BitmapFrame.Create(new Uri(filePath), BitmapCreateOptions.None, bitmapCacheOption); frame.Freeze(); isCorruptOrMissing = false; return(frame); } BitmapImage bitmap = new BitmapImage(); bitmap.BeginInit(); if (imageDimension == ImageDimensionEnum.UseWidth) { bitmap.DecodePixelWidth = desiredWidthOrHeight.Value; } else { bitmap.DecodePixelHeight = desiredWidthOrHeight.Value; } bitmap.CacheOption = bitmapCacheOption; bitmap.UriSource = new Uri(filePath); bitmap.EndInit(); bitmap.Freeze(); isCorruptOrMissing = false; return(bitmap); } catch (Exception exception) { // Optional messages for eventual debugging of catch errors, if (exception is InsufficientMemoryException) { TracePrint.PrintMessage(String.Format("ImageRow/LoadBitmap: exception getting bitmap from file: {0}\n.** Insufficient Memory Exception: {1}.\n--------------\n**StackTrace: {2}.\nXXXXXXXXXXXXXX\n\n", filePath, exception.Message, exception.StackTrace)); } else { // TraceDebug.PrintMessage(String.Format("ImageRow/LoadBitmap: General exception: {0}\n.**Unkonwn exception getting bitmap from file: {1}.\n--------------\n**StackTrace: {2}.\nXXXXXXXXXXXXXX\n\n", filePath, exception.Message, exception.StackTrace)); } isCorruptOrMissing = true; return(Constant.ImageValues.Corrupt.Value); } }
private async Task FilesSelectAndShowAsync() { if (this.DataHandler == null || this.DataHandler.FileDatabase == null) { TracePrint.PrintMessage("FilesSelectAndShow: Expected a file database to be available."); } await this.FilesSelectAndShowAsync(this.DataHandler.FileDatabase.ImageSet.FileSelection).ConfigureAwait(true); }
public void RemoveMarker(Marker marker) { int index = this.Markers.IndexOf(marker); if (index == -1) { TracePrint.PrintMessage("RemoveMarker: Expected marker to be present in list, but its not there."); return; } this.Markers.RemoveAt(index); }
// Delete the file, where we also try to back it up by moving it into the Deleted folder // TODO File deletion backups is problematic as files in different relative paths could have the same file name (overwritting possible, ambiguity). Perhaps mirror the file structure as otherwise a previously deleted file could be overwritten // CODECLEANUP Should this method really be part of an image row? public bool TryMoveFileToDeletedFilesFolder(string folderPath) { string sourceFilePath = this.GetFilePath(folderPath); if (!System.IO.File.Exists(sourceFilePath)) { return(false); // If there is no source file, its a missing file so we can't back it up } // Create a new target folder, if necessary. string deletedFilesFolderPath = Path.Combine(folderPath, Constant.File.DeletedFilesFolder); if (!Directory.Exists(deletedFilesFolderPath)) { Directory.CreateDirectory(deletedFilesFolderPath); } // Move the file to the backup location. string destinationFilePath = Path.Combine(deletedFilesFolderPath, this.File); if (System.IO.File.Exists(destinationFilePath)) { try { // Because move doesn't allow overwriting, delete the destination file if it already exists. System.IO.File.Delete(sourceFilePath); return(true); } catch (UnauthorizedAccessException exception) { TracePrint.PrintMessage("Could not delete " + sourceFilePath + Environment.NewLine + exception.Message + ": " + exception.ToString()); return(false); } } try { System.IO.File.Move(sourceFilePath, destinationFilePath); return(true); } catch (Exception exception) { // This may occur if for some reason we could not move the file, for example, if we have loaded the image in a way that it locks the file. // I've changed image loading to avoid this, but its something to watch out for. TracePrint.PrintMessage("Could not move " + sourceFilePath + Environment.NewLine + exception.Message + ": " + exception.ToString()); return(false); } }
// Delete sub-menu opening private void MenuItemDelete_SubmenuOpening(object sender, RoutedEventArgs e) { try { // Temporarily set the DeleteFlag search term so that it will be used to chec for DeleteFlag = true SearchTerm currentSearchTerm = this.DataHandler.FileDatabase.CustomSelection.SearchTerms.First(term => term.DataLabel == Constant.DatabaseColumn.DeleteFlag); SearchTerm tempSearchTerm = currentSearchTerm.Clone(); currentSearchTerm.DatabaseValue = "true"; currentSearchTerm.UseForSearching = true; currentSearchTerm.Operator = "="; //bool deletedImages = this.DataHandler.FileDatabase.ExistsRowThatMatchesSelectionForAllFilesOrConstrainedRelativePathFiles(FileSelectionEnum.MarkedForDeletion); //bool deletedImages = this.DataHandler.FileDatabase.CountAllFilesMatchingSelectionCondition(FileSelectionEnum.Custom) > 0; bool deletedImages = this.DataHandler.FileDatabase.ExistsFilesMatchingSelectionCondition(FileSelectionEnum.Custom); // Reset the DeleteFlag search term to its previous values currentSearchTerm.DatabaseValue = tempSearchTerm.DatabaseValue; currentSearchTerm.UseForSearching = tempSearchTerm.UseForSearching; currentSearchTerm.Operator = tempSearchTerm.Operator; this.MenuItemDeleteFiles.IsEnabled = deletedImages; this.MenuItemDeleteFilesAndData.IsEnabled = deletedImages; this.MenuItemDeleteFilesData.IsEnabled = deletedImages; // Enable the delete current file option only if we are not on the thumbnail grid this.MenuItemDeleteCurrentFileAndData.IsEnabled = this.MarkableCanvas.IsThumbnailGridVisible == false; // Only show this option if the thumbnail grid is visible this.MenuItemDeleteCurrentFile.IsEnabled = this.MarkableCanvas.IsThumbnailGridVisible == false && this.DataHandler.ImageCache.Current.IsDisplayable(this.FolderPath); this.MenuItemDeleteCurrentData.IsEnabled = this.MarkableCanvas.IsThumbnailGridVisible == false; } catch (Exception exception) { TracePrint.PrintMessage(String.Format("Delete submenu failed to open in Delete_SubmenuOpening. {0}", exception.ToString())); // This function was blowing up on one user's machine, but not others. // I couldn't figure out why, so I just put this fallback in here to catch that unusual case. this.MenuItemDeleteFiles.IsEnabled = true; this.MenuItemDeleteFilesAndData.IsEnabled = true; this.MenuItemDeleteFilesData.IsEnabled = true; this.MenuItemDeleteCurrentFile.IsEnabled = true; this.MenuItemDeleteCurrentFileAndData.IsEnabled = true; this.MenuItemDeleteCurrentData.IsEnabled = true; } }
private void MenuItemExportThisImage_Click(object sender, RoutedEventArgs e) { if (!this.DataHandler.ImageCache.Current.IsDisplayable(this.FolderPath)) { // Can't export the currently displayed image as a file Dialogs.MenuFileCantExportCurrentImageDialog(this); return; } // Get the file name of the current image string sourceFile = this.DataHandler.ImageCache.Current.File; // Set up a Folder Browser with some instructions using (SaveFileDialog dialog = new SaveFileDialog() { Title = "Export a copy of the currently displayed file", Filter = String.Format("*{0}|*{0}", Path.GetExtension(this.DataHandler.ImageCache.Current.File)), FileName = sourceFile, OverwritePrompt = true }) { // Display the Folder Browser dialog DialogResult result = dialog.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { // Set the source and destination file names, including the complete path string sourcePath = this.DataHandler.ImageCache.Current.GetFilePath(this.FolderPath); string destFileName = dialog.FileName; // Try to copy the source file to the destination, overwriting the destination file if it already exists. // And giving some feedback about its success (or failure) try { File.Copy(sourcePath, destFileName, true); this.StatusBar.SetMessage(sourceFile + " copied to " + destFileName); } catch (Exception exception) { TracePrint.PrintMessage(String.Format("Copy of '{0}' to '{1}' failed. {2}", sourceFile, destFileName, exception.ToString())); this.StatusBar.SetMessage(String.Format("Could not copy '{0}' for some reason.", sourceFile)); } } } }
/// <summary> /// Update the list of recent databases displayed under File -> Recent Databases. /// </summary> private void RecentFileSets_Refresh() { // remove image sets which are no longer present from the most recently used list // probably overkill to perform this check on every refresh rather than once at application launch, but it's not particularly expensive List <string> invalidPaths = new List <string>(); foreach (string recentImageSetPath in this.State.MostRecentImageSets) { if (File.Exists(recentImageSetPath) == false) { invalidPaths.Add(recentImageSetPath); } } foreach (string path in invalidPaths) { bool result = this.State.MostRecentImageSets.TryRemove(path); if (!result) { TracePrint.PrintMessage(String.Format("Removal of image set '{0}' no longer present on disk unexpectedly failed.", path)); } } // Enable the menu only when there are items in it and only if the load menu is also enabled (i.e., that we haven't loaded anything yet) this.MenuItemRecentImageSets.IsEnabled = this.State.MostRecentImageSets.Count > 0 && this.MenuItemLoadFiles.IsEnabled; this.MenuItemRecentImageSets.Items.Clear(); // add menu items most recently used image sets int index = 1; foreach (string recentImageSetPath in this.State.MostRecentImageSets) { // Create a menu item for each path MenuItem recentImageSetItem = new MenuItem(); recentImageSetItem.Click += this.MenuItemRecentImageSet_Click; recentImageSetItem.Header = String.Format("_{0} {1}", index++, recentImageSetPath); recentImageSetItem.ToolTip = recentImageSetPath; this.MenuItemRecentImageSets.Items.Add(recentImageSetItem); } }
// Delete sub-menu opening private void MenuItemDelete_SubmenuOpening(object sender, RoutedEventArgs e) { try { bool deletedImages = this.DataHandler.FileDatabase.RowExistsWhere(FileSelectionEnum.MarkedForDeletion); this.MenuItemDeleteFiles.IsEnabled = deletedImages; this.MenuItemDeleteFilesAndData.IsEnabled = deletedImages; // Enable the delete current file option only if we are not on the thumbnail grid this.MenuItemDeleteCurrentFileAndData.IsEnabled = this.MarkableCanvas.IsThumbnailGridVisible == false; // Only show this option if the thumbnail grid is visible this.MenuItemDeleteCurrentFile.IsEnabled = this.MarkableCanvas.IsThumbnailGridVisible == false && this.DataHandler.ImageCache.Current.IsDisplayable(this.FolderPath); } catch (Exception exception) { TracePrint.PrintMessage(String.Format("Delete submenu failed to open in Delete_SubmenuOpening. {0}", exception.ToString())); // This function was blowing up on one user's machine, but not others. // I couldn't figure out why, so I just put this fallback in here to catch that unusual case. this.MenuItemDeleteFiles.IsEnabled = true; this.MenuItemDeleteFilesAndData.IsEnabled = true; this.MenuItemDeleteCurrentFile.IsEnabled = true; this.MenuItemDeleteCurrentFileAndData.IsEnabled = true; } }
// Returns: // - the list of files whose dates have changed // - a collection of feedback information for each file whose dates were changed, each row detailing the file name and how the dates were changed // - the number of missing Files, if any private List <ImageRow> GetImageRowsWithChangedDates(IProgress <ProgressBarArguments> progress, int count, TimeZoneInfo imageSetTimeZone, ObservableCollection <DateTimeFeedbackTuple> feedbackRows, out int missingFiles) { List <ImageRow> filesToAdjust = new List <ImageRow>(); missingFiles = 0; for (int fileIndex = 0; fileIndex < count; ++fileIndex) { if (Token.IsCancellationRequested) { // A cancel was requested. Clear all pending changes and abort feedbackRows.Clear(); break; } // We will store the various times here ImageRow file = this.fileDatabase.FileTable[fileIndex]; DateTimeOffset originalDateTime = file.DateTimeIncorporatingOffset; string feedbackMessage = string.Empty; try { // Get the image (if its there), get the new dates/times, and add it to the list of images to be updated // Note that if the image can't be created, we will just to the catch. bool usingMetadataTimestamp = true; if (file.FileExists(this.fileDatabase.FolderPath) == false) { // The file does not exist. Generate a feedback message missingFiles++; } else { // Read the date from the file, and check to see if its different from the recorded date DateTimeAdjustmentEnum dateTimeAdjustment = file.TryReadDateTimeOriginalFromMetadata(this.fileDatabase.FolderPath, imageSetTimeZone); if (dateTimeAdjustment == DateTimeAdjustmentEnum.MetadataNotUsed) { // We couldn't read the metadata, so get a candidate date/time from the file info instead file.SetDateTimeOffsetFromFileInfo(this.fileDatabase.FolderPath); usingMetadataTimestamp = false; } DateTimeOffset rescannedDateTime = file.DateTimeIncorporatingOffset; bool sameDate = rescannedDateTime.Date == originalDateTime.Date; bool sameTime = rescannedDateTime.TimeOfDay == originalDateTime.TimeOfDay; bool sameUTCOffset = rescannedDateTime.Offset == originalDateTime.Offset; if (!(sameDate && sameTime && sameUTCOffset)) { // Date has been updated - add it to the queue of files to be processed, and generate a feedback message. filesToAdjust.Add(file); feedbackMessage = "\x2713"; // Checkmark feedbackMessage += DateTimeHandler.ToStringDisplayDateTime(originalDateTime) + " \x2192 " + DateTimeHandler.ToStringDisplayDateTime(rescannedDateTime); feedbackMessage += usingMetadataTimestamp ? " (read from metadata)" : " (read from file)"; feedbackRows.Add(new DateTimeFeedbackTuple(file.File, feedbackMessage)); } } } catch (Exception exception) { // This shouldn't happen, but just in case. TracePrint.PrintMessage(string.Format("Unexpected exception processing '{0}' in DateTimeReread. {1}", file.File, exception.ToString())); feedbackMessage += string.Format("\x2716 skipping: {0}", exception.Message); feedbackRows.Add(new DateTimeFeedbackTuple(file.File, feedbackMessage)); break; } // Update the progress bar every time interval to indicate what file we are working on TimeSpan intervalFromLastRefresh = DateTime.Now - this.lastRefreshDateTime; if (intervalFromLastRefresh > Constant.ThrottleValues.ProgressBarRefreshInterval) { int percentDone = Convert.ToInt32(fileIndex / Convert.ToDouble(count) * 100.0); progress.Report(new ProgressBarArguments(percentDone, String.Format("Pass 1: Checking dates for {0} / {1} files", fileIndex, count), true, false)); Thread.Sleep(Constant.ThrottleValues.RenderingBackoffTime); this.lastRefreshDateTime = DateTime.Now; } } return(filesToAdjust); }
// PEFORMANCE FILES SELECT AND SHOW CALLED TOO OFTEN, GIVEN THAT IT IS A SLOW OPERATION private async Task FilesSelectAndShowAsync(long imageID, FileSelectionEnum selection) { // change selection // if the data grid is bound the file database automatically updates its contents on SelectFiles() if (this.DataHandler == null) { TracePrint.PrintMessage("FilesSelectAndShow() should not be reachable with a null data handler. Is a menu item wrongly enabled?");; } if (this.DataHandler.FileDatabase == null) { TracePrint.PrintMessage("FilesSelectAndShow() should not be reachable with a null database. Is a menu item wrongly enabled?"); } // Select the files according to the given selection // Note that our check for missing actually checks to see if the file exists, // which is why its a different operation // PEFORMANCE - TWO SLOW OPERATIONS: FilesSelectAndShow invoking this.dataHandler.FileDatabase.SelectFile / .SelectMissingFilesFromCurrentlySelectedFiles Mouse.OverrideCursor = Cursors.Wait; if (selection == FileSelectionEnum.Missing) { // PERFORMANCE this can be slow if there are many files, as it checks every single file in the current database selection to see if it exists // However, it is not a mainstream operation so can be considered a lower priority place for optimization bool anyFilesMissing = this.DataHandler.FileDatabase.SelectMissingFilesFromCurrentlySelectedFiles(); if (anyFilesMissing == false) { // No files were missing. Inform the user, and don't change anything. Mouse.OverrideCursor = null; Dialogs.FileSelectionNoFilesAreMissingDialog(this); return; } } else { // If its a folder selection, record it so we can save it later in the image set table this.DataHandler.FileDatabase.ImageSet.SelectedFolder = selection == FileSelectionEnum.Folders ? this.DataHandler.FileDatabase.GetSelectedFolder() : String.Empty; // PERFORMANCE Select Files is a very slow operation as it runs a query over all files and returns everything it finds as datatables stored in memory. this.BusyCancelIndicator.EnableForSelection(true); await this.DataHandler.FileDatabase.SelectFilesAsync(selection).ConfigureAwait(true); this.BusyCancelIndicator.EnableForSelection(false); this.DataHandler.FileDatabase.BindToDataGrid(); } Mouse.OverrideCursor = null; if ((this.DataHandler.FileDatabase.CountAllCurrentlySelectedFiles < 1) && (selection != FileSelectionEnum.All)) { // Tell the user that we are resetting the selection to all files Dialogs.FileSelectionResettngSelectionToAllFilesDialog(this, selection); this.StatusBar.SetMessage("Resetting selection to All files."); selection = FileSelectionEnum.All; // PEFORMANCE: The standard select files operation in FilesSelectAndShow this.BusyCancelIndicator.EnableForSelection(true); await this.DataHandler.FileDatabase.SelectFilesAsync(selection).ConfigureAwait(true); this.BusyCancelIndicator.EnableForSelection(false); this.DataHandler.FileDatabase.BindToDataGrid(); } // Change the selection to reflect what the user selected. Update the menu state accordingly // Set the checked status of the radio button menu items to the selection. string status; switch (selection) { case FileSelectionEnum.All: status = "All files"; break; case FileSelectionEnum.Custom: status = "Custom selection"; break; case FileSelectionEnum.Dark: status = "Dark files"; break; case FileSelectionEnum.MarkedForDeletion: status = "Files marked for deletion"; break; case FileSelectionEnum.Folders: status = "Files in a specific folder"; break; case FileSelectionEnum.Missing: status = "Missing files"; break; case FileSelectionEnum.Ok: status = "Non-dark files"; break; default: throw new NotSupportedException(String.Format("Unhandled file selection {0}.", selection)); } // Show feedback of the status description in both the status bar and the data entry control panel title this.StatusBar.SetView(status); this.DataEntryControlPanel.Title = "Data entry for " + status; // Reset the Episodes, as it may change based on the current selection Episodes.Reset(); // Display the specified file or, if it's no longer selected, the next closest one // FileShow() handles empty image sets, so those don't need to be checked for here. // After a selection changes, set the slider to represent the index and the count of the current selection this.FileNavigatorSlider_EnableOrDisableValueChangedCallback(false); this.FileNavigatorSlider.Maximum = this.DataHandler.FileDatabase.CountAllCurrentlySelectedFiles; // Reset the slider to the size of images in this set if (this.FileNavigatorSlider.Maximum <= 50) { this.FileNavigatorSlider.IsSnapToTickEnabled = true; this.FileNavigatorSlider.TickFrequency = 1.0; } else { this.FileNavigatorSlider.IsSnapToTickEnabled = false; this.FileNavigatorSlider.TickFrequency = 0.02 * this.FileNavigatorSlider.Maximum; } // Reset the ThumbnailGrid selection after every change in the selection if (this.IsDisplayingMultipleImagesInOverview()) { this.MarkableCanvas.ThumbnailGrid.SelectInitialCellOnly(); } this.DataEntryControls.AutocompletionPopulateAllNotesWithFileTableValues(this.DataHandler.FileDatabase); // Always force an update after a selection this.FileShow(this.DataHandler.FileDatabase.GetFileOrNextFileIndex(imageID), true); // Update the status bar accordingly this.StatusBar.SetCurrentFile(this.DataHandler.ImageCache.CurrentRow + 1); // We add 1 because its a 0-based list this.StatusBar.SetCount(this.DataHandler.FileDatabase.CountAllCurrentlySelectedFiles); this.FileNavigatorSlider_EnableOrDisableValueChangedCallback(true); this.DataHandler.FileDatabase.ImageSet.FileSelection = selection; // Remember the current selection }
/// <summary> /// Add user interface event handler callbacks for (possibly invisible) controls /// </summary> private void SetUserInterfaceCallbacks() { // Add data entry callbacks to all editable controls. When the user changes an image's attribute using a particular control, // the callback updates the matching field for that image in the database. DataEntryNote date = null; DataEntryDateTime dateTime = null; DataEntryNote time = null; foreach (KeyValuePair <string, DataEntryControl> pair in this.DataEntryControls.ControlsByDataLabel) { string controlType = this.DataHandler.FileDatabase.FileTableColumnsByDataLabel[pair.Key].ControlType; switch (controlType) { case Constant.Control.Counter: DataEntryCounter counter = (DataEntryCounter)pair.Value; counter.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; counter.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; counter.Container.MouseEnter += this.CounterControl_MouseEnter; counter.Container.MouseLeave += this.CounterControl_MouseLeave; counter.LabelControl.Click += this.CounterControl_Click; break; case Constant.Control.Flag: case Constant.DatabaseColumn.DeleteFlag: DataEntryFlag flag = (DataEntryFlag)pair.Value; flag.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; flag.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; break; case Constant.Control.FixedChoice: case Constant.DatabaseColumn.ImageQuality: DataEntryChoice choice = (DataEntryChoice)pair.Value; choice.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; choice.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; break; case Constant.Control.Note: case Constant.DatabaseColumn.Date: case Constant.DatabaseColumn.File: case Constant.DatabaseColumn.Folder: case Constant.DatabaseColumn.RelativePath: case Constant.DatabaseColumn.Time: DataEntryNote note = (DataEntryNote)pair.Value; note.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; note.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; if (controlType == Constant.DatabaseColumn.Date) { date = note; } if (controlType == Constant.DatabaseColumn.Time) { time = note; } break; case Constant.DatabaseColumn.DateTime: dateTime = (DataEntryDateTime)pair.Value; dateTime.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; dateTime.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; break; case Constant.DatabaseColumn.UtcOffset: DataEntryUtcOffset utcOffset = (DataEntryUtcOffset)pair.Value; utcOffset.ContentControl.PreviewMouseDown += this.ContentControl_MouseDown; utcOffset.ContentControl.PreviewKeyDown += this.ContentCtl_PreviewKeyDown; break; default: TracePrint.PrintMessage(String.Format("Unhandled control type '{0}' in SetUserInterfaceCallbacks.", controlType)); break; } } // if needed, link date and time controls to datetime control if (dateTime != null && date != null) { dateTime.DateControl = date; } if (dateTime != null && time != null) { dateTime.TimeControl = time; } }
/// <summary> /// Generate the controls based upon the control descriptions found in the template /// </summary>> public void CreateControls(FileDatabase database, DataEntryHandler dataEntryPropagator) { // Check the arguments for null ThrowIf.IsNullArgument(dataEntryPropagator, nameof(dataEntryPropagator)); ThrowIf.IsNullArgument(database, nameof(database)); // 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.Controls.Clear(); this.ControlsByDataLabel.Clear(); this.ControlGrid.Inlines.Clear(); 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; } DataEntryControl controlToAdd; if (control.Type == Constant.DatabaseColumn.DateTime) { DataEntryDateTime dateTimeControl = new DataEntryDateTime(control, this); controlToAdd = dateTimeControl; } else if (control.Type == Constant.DatabaseColumn.File || control.Type == Constant.DatabaseColumn.RelativePath || control.Type == Constant.DatabaseColumn.Folder || control.Type == Constant.DatabaseColumn.Date || control.Type == Constant.DatabaseColumn.Time || control.Type == Constant.Control.Note) { // standard controls rendering as notes aren't editable by the user, so we don't need autocompletions on tht Dictionary <string, string> autocompletions = null; bool readOnly = control.Type != Constant.Control.Note; if (readOnly == false) { autocompletions = new Dictionary <string, string>(); } DataEntryNote noteControl = new DataEntryNote(control, autocompletions, this) { ContentReadOnly = readOnly }; controlToAdd = noteControl; } else if (control.Type == Constant.Control.Flag || control.Type == Constant.DatabaseColumn.DeleteFlag) { DataEntryFlag flagControl = new DataEntryFlag(control, this); controlToAdd = flagControl; } else if (control.Type == Constant.Control.Counter) { DataEntryCounter counterControl = new DataEntryCounter(control, this); controlToAdd = counterControl; } else if (control.Type == Constant.Control.FixedChoice) { DataEntryChoice choiceControl = new DataEntryChoice(control, this); controlToAdd = choiceControl; } else if (control.Type == Constant.DatabaseColumn.ImageQuality) { DataEntryChoice choiceControl = new DataEntryChoice(control, this); choiceControl.HideItems(new List <string> { FileSelectionEnum.Corrupted.ToString(), FileSelectionEnum.Missing.ToString() }); controlToAdd = choiceControl; } else if (control.Type == Constant.DatabaseColumn.UtcOffset) { DataEntryUtcOffset utcOffsetControl = new DataEntryUtcOffset(control, this) { ContentReadOnly = true }; controlToAdd = utcOffsetControl; } else { TracePrint.PrintMessage(String.Format("Unhandled control type {0} in CreateControls.", control.Type)); continue; } this.ControlGrid.Inlines.Add(controlToAdd.Container); this.Controls.Add(controlToAdd); this.ControlsByDataLabel.Add(control.DataLabel, controlToAdd); } // Redundant check as for some reason CA1062 was still showing up as a warning. ThrowIf.IsNullArgument(dataEntryPropagator, nameof(dataEntryPropagator)); dataEntryPropagator.SetDataEntryCallbacks(this.ControlsByDataLabel); this.dataEntryHandler = dataEntryPropagator; }