public ImageLoader(string imageSetFolderPath, string relativePath, FileInfo fileInfo, DataEntryHandler dataHandler) { this.fileInfo = fileInfo; this.dataHandler = dataHandler; this.imageSetFolderPath = imageSetFolderPath; this.relativePath = relativePath; }
private DateTimePicker CreateDateTimePicker(ControlRow control) { DateTimePicker dateTimePicker = new DateTimePicker() { ToolTip = control.Tooltip, Width = control.Width, CultureInfo = System.Globalization.CultureInfo.CreateSpecificCulture("en-US") }; DataEntryHandler.Configure(dateTimePicker, Constant.ControlDefault.DateTimeValue.DateTime); dateTimePicker.GotFocus += this.Control_GotFocus; dateTimePicker.LostFocus += this.Control_LostFocus; return(dateTimePicker); }
private void Window_Loaded(object sender, RoutedEventArgs e) { // Set up a progress handler that will update the progress bar this.InitalizeProgressHandler(this.BusyCancelIndicator); // Set up the initial UI and values // Get the image filename and image and display them this.FileName.Content = this.ImageToCorrect.File; this.FileName.ToolTip = this.ImageToCorrect.File; this.SampleImage.Source = this.ImageToCorrect.LoadBitmap(this.fileDatabase.FolderPath, out bool isCorruptOrMissing); // Configure datetime picker to the initial date on the images plus callbacks this.initialDate = this.ImageToCorrect.DateTimeIncorporatingOffset; this.OriginalDate.Content = DateTimeHandler.ToStringDisplayDateTime(this.initialDate); DataEntryHandler.Configure(this.DateTimePicker, this.initialDate.DateTime); this.DateTimePicker.ValueChanged += this.DateTimePicker_ValueChanged; }
private void Window_Loaded(object sender, RoutedEventArgs e) { // Set up a progress handler that will update the progress bar this.InitalizeProgressHandler(this.BusyCancelIndicator); // Set up the initial UI and values this.latestImageDateTime = DateTimeOffset.MinValue; this.earliestImageDateTime = DateTimeOffset.MaxValue; // Search the images for the two images with the earliest and latest data/time date ImageRow latestImageRow = null; ImageRow earliestImageRow = null; foreach (ImageRow image in this.fileDatabase.FileTable) { DateTimeOffset currentImageDateTime = image.DateTimeIncorporatingOffset; // If the current image's date is later, then it is a candidate latest image if (currentImageDateTime >= this.latestImageDateTime) { latestImageRow = image; this.latestImageDateTime = currentImageDateTime; } // If the current image's date is earlier, then it is a candidate earliest image if (currentImageDateTime <= this.earliestImageDateTime) { earliestImageRow = image; this.earliestImageDateTime = currentImageDateTime; } } // At this point, we should have succeeded getting the oldest and newest data/time // Configure the earliest date (in datetime picker) and its image this.earliestImageName.Content = earliestImageRow.File; this.earliestImageDate.Content = DateTimeHandler.ToStringDisplayDateTime(this.earliestImageDateTime); this.imageEarliest.Source = earliestImageRow.LoadBitmap(this.fileDatabase.FolderPath, out bool isCorruptOrMissing); // Configure the latest date (in datetime picker) and its image this.latestImageName.Content = latestImageRow.File; DataEntryHandler.Configure(this.dateTimePickerLatestDateTime, this.latestImageDateTime.DateTime); this.dateTimePickerLatestDateTime.ValueChanged += this.DateTimePicker_ValueChanged; this.imageLatest.Source = latestImageRow.LoadBitmap(this.fileDatabase.FolderPath, out isCorruptOrMissing); }
// Receive an event containing new image processing parameters. // Store these parameters and then try to update the image public async void AdjustImage_EventHandler(object sender, ImageAdjusterEventArgs e) { if (e == null) { // Shouldn't happen, but... return; } string path = DataEntryHandler.TryGetFilePathFromGlobalDataHandler(); if (String.IsNullOrEmpty(path)) { // The file cannot be opened or is not displayable. // Signal change in image state, which essentially says there is no displayable image to adjust (consumed by ImageAdjuster) this.OnImageStateChanged(new ImageStateEventArgs(false)); // Signal change in image state (consumed by ImageAdjuster) return; } if (e.OpenExternalViewer) { // The event says to open an external photo viewer. Try to do so. // Note that we don't do any image processing on this event if if this is the case. if (ProcessExecution.TryProcessStart(path) == false) { // Can't open the image file with an external view. Note that file must exist at this point as we checked for that above. Dialogs.MarkableCanvasCantOpenExternalPhotoViewerDialog(Util.GlobalReferences.MainWindow, Path.GetExtension(path)); } return; } // Process the image based on the current image processing arguments. if (e.ForceUpdate == false && (e.Contrast == this.lastContrast && e.Brightness == this.lastBrightness && e.DetectEdges == this.lastDetectEdges && e.Sharpen == this.lastSharpen && e.UseGamma == this.lastUseGamma && e.GammaValue == this.lastGammaValue)) { // If there is no change from the last time we processed an image, abort as it would not make any difference to what the user sees return; } this.contrast = e.Contrast; this.brightness = e.Brightness; this.detectEdges = e.DetectEdges; this.sharpen = e.Sharpen; this.useGamma = e.UseGamma; this.gammaValue = e.GammaValue; this.timerImageProcessingUpdate.Start(); await UpdateAndProcessImage().ConfigureAwait(true); }
// Update the image according to the image processing parameters. private async Task UpdateAndProcessImage() { // If its processing the image, try again later (via the time), if (this.Processing) { return; } try { string path = DataEntryHandler.TryGetFilePathFromGlobalDataHandler();; if (String.IsNullOrEmpty(path)) { // If we cannot get a valid file, there is no image to manipulate. // So abort and signal a change in image state that says there is no displayable image to adjust (consumed by ImageAdjuster) this.OnImageStateChanged(new ImageStateEventArgs(false)); } // Set the state to Processing is used to indicate that other attempts to process the image should be aborted util this is done. this.Processing = true; using (MemoryStream imageStream = new MemoryStream(File.ReadAllBytes(path))) { // Remember the currently selected image processing states, so we can compare them later for changes this.lastBrightness = this.brightness; this.lastContrast = this.contrast; this.lastSharpen = this.sharpen; this.lastDetectEdges = this.detectEdges; this.lastUseGamma = this.useGamma; this.lastGammaValue = this.gammaValue; BitmapFrame bf = await ImageProcess.StreamToImageProcessedBitmap(imageStream, this.brightness, this.contrast, this.sharpen, this.detectEdges, this.useGamma, this.gammaValue).ConfigureAwait(true); if (bf != null) { this.ImageToDisplay.Source = await ImageProcess.StreamToImageProcessedBitmap(imageStream, this.brightness, this.contrast, this.sharpen, this.detectEdges, this.useGamma, this.gammaValue).ConfigureAwait(true); } } } catch { // We failed on this image. To avoid this happening again, // Signal change in image state, which essentially says there is no adjustable image (consumed by ImageAdjuster) this.OnImageStateChanged(new ImageStateEventArgs(false)); } this.Processing = false; }
public ImageSetLoader(string imageSetFolderPath, IEnumerable <FileInfo> fileInfos, DataEntryHandler dataHandler) { // Check the arguments for null ThrowIf.IsNullArgument(dataHandler, nameof(dataHandler)); ImagesSkippedAsFilePathTooLong = new List <string>(); // Don't add a file if it already exists in the database. // Rather than check every file one by one to see if it exists in the database // - get all the current files in the database (as existing full paths) in a single database call, // - create a new file list (fileInfoArray) that only adds files (as fileInfos) that are NOT present in the database. HashSet <string> existingPaths; using (FileTable filetable = dataHandler.FileDatabase.SelectAllFiles()) { existingPaths = new HashSet <string>(from file in filetable select Path.Combine(imageSetFolderPath, Path.Combine(file.RelativePath, file.File)).ToLowerInvariant()); } FileInfo[] filesToAddInfoArray = null; filesToAddInfoArray = (from fileInfo in fileInfos where existingPaths.Contains(fileInfo.FullName.ToLowerInvariant()) == false select fileInfo).OrderBy(f => f.FullName).ToArray(); this.ImagesToLoad = filesToAddInfoArray.Length; // The queue will take image rows ready for insertion to the second pass // The eventindicates explicitly when the first pass is done. ConcurrentQueue <ImageRow> databaseInsertionQueue = new ConcurrentQueue <ImageRow>(); // We trim as well, as it handles both the case where it already has a trailing \ and when it is missing it. // For example, if we opened a template in a top level drive, it would have a following '\' string absolutePathPart = imageSetFolderPath.TrimEnd(Path.DirectorySeparatorChar) + @"\"; // Pass 1 this.pass1 = new Task(() => { List <Task> loadTasks = new List <Task>(); // Fan out the loader tasks foreach (FileInfo fileInfo in filesToAddInfoArray) { // Parse the relative path from the full name. // As GetDirectoryName does not end with a \ on a file name, we add the' '\' as needed string directoryName = String.Empty; try { directoryName = Path.GetDirectoryName(fileInfo.FullName); if (directoryName.EndsWith(@"\") == false) { directoryName += @"\"; } } catch (System.IO.PathTooLongException) { // If the file path is too long, skip the file. // Also, add its folder name (if it isn't already there) to a list so we can // later show a meaningful error message to the user that these files were skipped. // We do the folder name as otherwise the number of images could be overwhelming. string path = fileInfo.FullName.Substring(0, fileInfo.FullName.LastIndexOf(("\\"))); if (ImagesSkippedAsFilePathTooLong.Contains(path) == false) { ImagesSkippedAsFilePathTooLong.Add(path); } continue; } string relativePath = directoryName.Replace(absolutePathPart, string.Empty).TrimEnd(Path.DirectorySeparatorChar); ImageLoader loader = new ImageLoader(imageSetFolderPath, relativePath, fileInfo, dataHandler); Task loaderTask = loader.LoadImageAsync(() => { // Both of these operations are atomic, the specific number and the specific loader at any given // time may not coorespond. Interlocked.Increment(ref this.imagesLoaded); this.LastLoadComplete = loader; if (loader.RequiresDatabaseInsert) { // This requires database insertion. Enqueue for pass 2 // Note that there is no strict ordering here, anything may finish and insert in // any order. By sorting the file infos above, things that sort first in the database should // be done first, BUT THIS MAY REQUIRE ADDITIONAL FINESSE TO KEEP THE EXPLICIT ORDER CORRECT. databaseInsertionQueue.Enqueue(loader.File); Interlocked.Increment(ref this.imagesToInsert); } }); loadTasks.Add(loaderTask); } Task.WaitAll(loadTasks.ToArray()); }); // End Pass 1 // Pass 2 this.pass2 = new Task(() => { // This pass2 starts after pass1 is fully complete List <ImageRow> imagesToInsert = databaseInsertionQueue.OrderBy(f => Path.Combine(f.RelativePath, f.File)).ToList(); dataHandler.FileDatabase.AddFiles(imagesToInsert, (ImageRow file, int fileIndex) => { this.LastInsertComplete = file; this.LastIndexInsertComplete = fileIndex; }); }); // End Pass 2 }
// Move the focus (usually because of tabbing or shift-tab) // It cycles between the data entry controls and the CopyPrevious button private void MoveFocusToNextOrPreviousControlOrCopyPreviousButton(bool moveToPreviousControl) { // identify the currently selected control // if focus is currently set to the canvas this defaults to the first or last control, as appropriate int currentControl = moveToPreviousControl ? this.DataEntryControls.Controls.Count : -1; Type type; IInputElement focusedElement = FocusManager.GetFocusedElement(this); if (focusedElement != null) { type = focusedElement.GetType(); // If we are moving the focus from outside to one of the controls in the data panel or the copy previous button, // then try to restore the focus to the last control that had the focus. if (Constant.Control.KeyboardInputTypes.Contains(type) == false && focusedElement != this.CopyPreviousValuesButton) { if (this.lastControlWithFocus != null && this.lastControlWithFocus.IsEnabled == true) { Keyboard.Focus(this.lastControlWithFocus); this.CopyPreviousValuesSetEnableStatePreviewsAndGlowsAsNeeded(); return; } } // Otherwise, try to find the control that has the current focus if (Constant.Control.KeyboardInputTypes.Contains(type)) { if (DataEntryHandler.TryFindFocusedControl(focusedElement, out DataEntryControl focusedControl)) { int index = 0; foreach (DataEntryControl control in this.DataEntryControls.Controls) { if (Object.ReferenceEquals(focusedControl, control)) { // We found it, so no need to look further currentControl = index; break; } ++index; } } } } // Then move to the next or previous control as available Func <int, int> incrementOrDecrement; if (moveToPreviousControl) { incrementOrDecrement = (int index) => { return(--index); }; } else { incrementOrDecrement = (int index) => { return(++index); }; } for (currentControl = incrementOrDecrement(currentControl); currentControl > -1 && currentControl < this.DataEntryControls.Controls.Count; currentControl = incrementOrDecrement(currentControl)) { DataEntryControl control = this.DataEntryControls.Controls[currentControl]; if (control.ContentReadOnly == false && control.IsContentControlEnabled == true && this.IsControlIncludedInTabOrder(control)) { this.lastControlWithFocus = control.Focus(this); // There is a bug with Avalon: when the data control pane is floating the focus does not go to it via the above call // (although it does when its docked). // Setting the focus to the actual content control seems to fix it. control.GetContentControl.Focus(); return; } } // if we've gone thorugh all the controls and couldn't set the focus, then we must be at the beginning or at the end. if (this.CopyPreviousValuesButton.IsEnabled) { // So set the focus to the Copy PreviousValuesButton, unless it is disabled. this.CopyPreviousValuesButton.Focus(); this.lastControlWithFocus = this.CopyPreviousValuesButton; this.CopyPreviousValuesSetEnableStatePreviewsAndGlowsAsNeeded(); } else { // Skip the CopyPreviousValuesButton, as it is disabled. DataEntryControl candidateControl = moveToPreviousControl ? this.DataEntryControls.Controls.Last() : this.DataEntryControls.Controls.First(); if (moveToPreviousControl) { // Find the LAST control foreach (DataEntryControl control in this.DataEntryControls.Controls) { if (control.ContentReadOnly == false) { candidateControl = control; } } } else { // Find the FIRST control foreach (DataEntryControl control in this.DataEntryControls.Controls) { if (control.ContentReadOnly == false) { candidateControl = control; break; } } } if (candidateControl != null) { this.lastControlWithFocus = candidateControl.Focus(this); } } }