public static bool TryFindFocusedControl(IInputElement focusedElement, out DataEntryControl focusedControl) { if (focusedElement is FrameworkElement focusedFrameworkElement) { focusedControl = (DataEntryControl)focusedFrameworkElement.Tag; if (focusedControl != null) { return(true); } // for complex controls which dynamic generate child controls, such as date time pickers, the tag of the focused element can't be set // so try to locate a parent of the focused element with a tag indicating the control FrameworkElement parent = null; if (focusedFrameworkElement.Parent != null && focusedFrameworkElement.TemplatedParent is FrameworkElement) { parent = (FrameworkElement)focusedFrameworkElement.Parent; } else if (focusedFrameworkElement.TemplatedParent != null && focusedFrameworkElement.TemplatedParent is FrameworkElement element) { parent = element; } if (parent != null) { return(DataEntryHandler.TryFindFocusedControl(parent, out focusedControl)); } } focusedControl = null; return(false); }
// Paste the contents of the clipboard into the current or selected controls // Note that we don't do any checks against the control's type, as that would be handled by the menu enablement protected virtual void MenuItemPasteFromClipboard_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } string newContent = Clipboard.GetText().Trim(); if (control is DataEntryCounter _) { // For counters, removing any leading 0's, but if this ends up with an empty string, then revert to 0 newContent = newContent.TrimStart(new Char[] { '0' }); if (string.IsNullOrEmpty(newContent)) { newContent = "0"; } } control.SetContentAndTooltip(newContent); this.UpdateRowsDependingOnThumbnailGridState(control.DataLabel, newContent); }
// Copy the current value of this control to all images protected virtual void MenuItemCopyCurrentValueToAll_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } // Display a dialog box that explains what will happen. Arguments indicate how many files will be affected, and is tuned to the type of control bool checkForZero = control is DataEntryCounter; int filesAffected = this.FileDatabase.CountAllCurrentlySelectedFiles; if (Dialogs.DataEntryConfirmCopyCurrentValueToAllDialog(Application.Current.MainWindow, control.Content, filesAffected, checkForZero) != true) { return; } // Update all files to match the value of the control (identified by the data label) in the currently selected image row. Mouse.OverrideCursor = Cursors.Wait; ImageRow imageRow = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.Current : this.FileDatabase.FileTable[this.ThumbnailGrid.GetSelected()[0]]; this.FileDatabase.UpdateFiles(imageRow, control.DataLabel); Mouse.OverrideCursor = null; }
// Return true if there is a non-empty value available public bool IsCopyFromLastNonEmptyValuePossible(DataEntryControl control) { // Check the arguments for null ThrowIf.IsNullArgument(control, nameof(control)); bool checkCounter = control is DataEntryCounter; bool checkFlag = control is DataEntryFlag; int nearestRowWithCopyableValue = -1; for (int fileIndex = this.ImageCache.CurrentRow - 1; fileIndex >= 0; fileIndex--) { // Search for the row with some value in it, starting from the previous row string valueToCopy = this.FileDatabase.FileTable[fileIndex].GetValueDatabaseString(control.DataLabel); if (String.IsNullOrWhiteSpace(valueToCopy) == false) { // for flags, we skip over falses // for counters, we skip over 0 // for all others, any value will work as long as its not null or white space if ((checkFlag && !valueToCopy.Equals("false")) || (checkCounter && !valueToCopy.Equals("0")) || (!checkCounter && !checkFlag)) { nearestRowWithCopyableValue = fileIndex; // We found a non-empty value break; } } } return(nearestRowWithCopyableValue >= 0); }
// When a flag changes, update the particular flag field(s) in the database private void FlagControl_CheckedChanged(object sender, RoutedEventArgs e) { if (this.IsProgrammaticControlUpdate) { return; } CheckBox checkBox = (CheckBox)sender; DataEntryControl control = (DataEntryControl)checkBox.Tag; string value = ((bool)checkBox.IsChecked) ? Constant.BooleanValue.True : Constant.BooleanValue.False; control.SetContentAndTooltip(value); this.UpdateRowsDependingOnThumbnailGridState(control.DataLabel, control.Content); }
// When the number in a particular counter box changes, update the particular counter field(s) in the database private void CounterControl_ValueChanged(object sender, RoutedPropertyChangedEventArgs <object> e) { if (this.IsProgrammaticControlUpdate) { return; } IntegerUpDown integerUpDown = (IntegerUpDown)sender; // Get the key identifying the control, and then add its value to the database DataEntryControl control = (DataEntryControl)integerUpDown.Tag; control.SetContentAndTooltip(integerUpDown.Value.ToString()); this.UpdateRowsDependingOnThumbnailGridState(control.DataLabel, control.Content); }
// Copy the value of the current control to the clipboard protected virtual void MenuItemCopyToClipboard_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } Clipboard.SetText(control.Content); }
// Return true if there is a non-empty value available public bool IsCopyFromLastNonEmptyValuePossible(DataEntryControl control) { int currentIndex = 0; // So we can print the value in the catch // Check the arguments for null ThrowIf.IsNullArgument(control, nameof(control)); bool checkCounter = control is DataEntryCounter; bool checkFlag = control is DataEntryFlag; int nearestRowWithCopyableValue = -1; // Its in a try/catch as very very occassionally we get a 'system.indexoutofrangeexception' try { // The current row depends on wheter we are in the thumbnail grid or the normal view int currentRow = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.CurrentRow : this.ThumbnailGrid.GetSelected()[0]; for (int fileIndex = currentRow - 1; fileIndex >= 0; fileIndex--) { currentIndex = fileIndex; // Search for the row with some value in it, starting from the previous row string valueToCopy = this.FileDatabase.FileTable[fileIndex].GetValueDatabaseString(control.DataLabel); if (String.IsNullOrWhiteSpace(valueToCopy) == false) { // for flags, we skip over falses // for counters, we skip over 0 // for all others, any value will work as long as its not null or white space if ((checkFlag && !valueToCopy.Equals("false")) || (checkCounter && !valueToCopy.Equals("0")) || (!checkCounter && !checkFlag)) { nearestRowWithCopyableValue = fileIndex; // We found a non-empty value break; } } } } catch (IndexOutOfRangeException e) { // I don't know why we get this occassional error, so this is an attempt to print out the result so we can debug it System.Diagnostics.Debug.Print(String.Format("IsCopyFromLastNonEmptyValuePossible: IndexOutOfRange Exception, where index is: {0}{1}{2}", currentIndex, Environment.NewLine, e.Message)); return(nearestRowWithCopyableValue >= 0); } return(nearestRowWithCopyableValue >= 0); }
// Propagate the current value of this control forward from this point across the current set of selected images protected virtual void MenuItemPropagateForward_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } int currentRowIndex = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.CurrentRow : this.ThumbnailGrid.GetSelected()[0]; int imagesAffected = this.FileDatabase.CountAllCurrentlySelectedFiles - currentRowIndex - 1; if (imagesAffected == 0) { // Display a dialog box saying there is nothing to copy forward. // Note that this should never be displayed, as the menu shouldn't be highlit if we are on the last image // But just in case... Dialogs.DataEntryNothingToCopyForwardDialog(Application.Current.MainWindow); return; } // Display the appropriate dialog box that explains what will happen. Arguments indicate how many files will be affected, and is tuned to the type of control ImageRow imageRow = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.Current : this.FileDatabase.FileTable[this.ThumbnailGrid.GetSelected()[0]]; string valueToCopy = imageRow.GetValueDisplayString(control.DataLabel); bool checkForZero = control is DataEntryCounter; if (Dialogs.DataEntryConfirmCopyForwardDialog(Application.Current.MainWindow, valueToCopy, imagesAffected, checkForZero) != true) { return; } // Update the files from the next row (as we are copying from the current row) to the end. Mouse.OverrideCursor = Cursors.Wait; int nextRowIndex = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.CurrentRow + 1 : this.ThumbnailGrid.GetSelected()[0] + 1; this.FileDatabase.UpdateFiles(imageRow, control.DataLabel, nextRowIndex, this.FileDatabase.CountAllCurrentlySelectedFiles - 1); Mouse.OverrideCursor = null; }
// When a choice changes, update the particular choice field(s) in the database private void ChoiceControl_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (this.IsProgrammaticControlUpdate) { return; } ComboBox comboBox = (ComboBox)sender; if (comboBox.SelectedItem == null) { // no item selected (probably the user cancelled) return; } // Get the key identifying the control, and then add its value to the database DataEntryControl control = (DataEntryControl)comboBox.Tag; control.SetContentAndTooltip(((ComboBoxItem)comboBox.SelectedItem).Content.ToString()); this.UpdateRowsDependingOnThumbnailGridState(control.DataLabel, control.Content); }
// Copy the value of the current control to the clipboard protected virtual void MenuItemCopyToClipboard_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } // Its in a try / catch as one user reported an unusual error: OpenClipboardFailed try { Clipboard.SetText(control.Content); } catch { System.Diagnostics.Debug.Print("Error in setting text in clipboard (see MenuItemCopyToClipboard_Click in DataEntryHandler"); } }
// Enable or disable particular context menu items protected virtual void Container_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); StackPanel stackPanel = (StackPanel)sender; DataEntryControl control = (DataEntryControl)stackPanel.Tag; MenuItem menuItemCopyToAll = (MenuItem)stackPanel.ContextMenu.Items[DataEntryHandler.CopyToAllIndex]; MenuItem menuItemCopyForward = (MenuItem)stackPanel.ContextMenu.Items[DataEntryHandler.CopyForwardIndex]; MenuItem menuItemPropagateFromLastValue = (MenuItem)stackPanel.ContextMenu.Items[DataEntryHandler.PropagateFromLastValueIndex]; MenuItem menuItemCopyToClipboard = (MenuItem)stackPanel.ContextMenu.Items[DataEntryHandler.CopyToClipboardIndex]; MenuItem menuItemPasteFromClipboard = (MenuItem)stackPanel.ContextMenu.Items[DataEntryHandler.PasteFromClipboardIndex]; // Behaviour: // - if the ThumbnailInCell is visible, disable Copy to all / Copy forward / Propagate if a single item isn't selected // - otherwise enable the menu item only if the resulting action is coherent bool enabledIsPossible = this.ThumbnailGrid.IsVisible == false || this.ThumbnailGrid.SelectedCount() == 1; menuItemCopyToAll.IsEnabled = enabledIsPossible; menuItemCopyForward.IsEnabled = enabledIsPossible && (menuItemCopyForward.IsEnabled = this.IsCopyForwardPossible()); menuItemPropagateFromLastValue.IsEnabled = enabledIsPossible && this.IsCopyFromLastNonEmptyValuePossible(control); // Enable Copy menu if // - its not empty / white space and not in the overview with different contents (i.e., ellipsis is showing) menuItemCopyToClipboard.IsEnabled = !(String.IsNullOrWhiteSpace(control.Content) || control.Content == Constant.Unicode.Ellipsis); // Enable Paste menu only if // - the clipboard is not empty or white space, // - the string matches the contents expected by the control's type // - we are not in the overview with different contents selected (i.e., ellipsis is showing) // Its in a try / catch as one user reported an unusual error: OpenClipboardFailed string clipboardText; try { clipboardText = Clipboard.GetText().Trim(); } catch { clipboardText = String.Empty; System.Diagnostics.Debug.Print("Error in setting text in clipboard (see Container_PreviewMouseRightButtonDown in DataEntryHandler"); } if (String.IsNullOrEmpty(clipboardText)) { menuItemPasteFromClipboard.IsEnabled = false; } else { if (control is DataEntryNote _) { // Any string is valid menuItemPasteFromClipboard.IsEnabled = true; } else if (control is DataEntryFlag _) { // Only true / false is valid menuItemPasteFromClipboard.IsEnabled = (clipboardText == "true" || clipboardText == "false"); } else if (control is DataEntryCounter _) { // Only a positive integer is valid menuItemPasteFromClipboard.IsEnabled = Int32.TryParse(clipboardText, out int x) && x >= 0; } else if (control is DataEntryChoice choiceControl) { // Only a value present as a menu choice is valid menuItemPasteFromClipboard.IsEnabled = false; ComboBox comboBox = choiceControl.ContentControl; foreach (ComboBoxItem cbi in comboBox.Items) { if (clipboardText == ((string)cbi.Content).Trim()) { // We found a matching value, so pasting is possible menuItemPasteFromClipboard.IsEnabled = true; break; } } } // Alter the paste header to show the text that will be pasted e.g Paste 'Lion' if (menuItemPasteFromClipboard.IsEnabled) { if (control is DataEntryCounter _) { // removing any leading 0's, but if its empty make it a 0 clipboardText = clipboardText.TrimStart(new Char[] { '0' }); if (string.IsNullOrEmpty(clipboardText)) { clipboardText = "0"; } } menuItemPasteFromClipboard.Header = "Paste '" + (clipboardText.Length > 20 ? clipboardText.Substring(0, 20) + Constant.Unicode.Ellipsis : clipboardText) + "'"; } else { // Since there is nothing in the clipboard, just show 'Paste' menuItemPasteFromClipboard.Header = "Paste"; } // Alter the copy header to show the text that will be copied, i.e. Copy 'Lion' if (menuItemCopyToClipboard.IsEnabled) { string content = control.Content.Trim(); menuItemCopyToClipboard.Header = "Copy '" + (content.Length > 20 ? content.Substring(0, 20) + Constant.Unicode.Ellipsis : content) + "'"; } else { // Since there an empty string to Copy, just show 'Copy' menuItemCopyToClipboard.Header = "Copy"; } } }
// Menu selections for propagating or copying the current value of this control to all images // Copy the last non-empty value in this control preceding this file up to the current image protected virtual void MenuItemPropagateFromLastValue_Click(object sender, RoutedEventArgs e) { // Check the arguments for null ThrowIf.IsNullArgument(sender, nameof(sender)); // Get the chosen data entry control DataEntryControl control = (DataEntryControl)((MenuItem)sender).Tag; if (control == null) { return; } bool checkForZero = control is DataEntryCounter; bool isFlag = control is DataEntryFlag; int indexToCopyFrom = -1; ImageRow valueSource = null; string valueToCopy = checkForZero ? "0" : String.Empty; // Search for the row with some value in it, starting from the previous row int currentRowIndex = (this.ThumbnailGrid.IsVisible == false) ? this.ImageCache.CurrentRow : this.ThumbnailGrid.GetSelected()[0]; for (int previousIndex = currentRowIndex - 1; previousIndex >= 0; previousIndex--) { ImageRow file = this.FileDatabase.FileTable[previousIndex]; if (file == null) { continue; } valueToCopy = file.GetValueDatabaseString(control.DataLabel); if (valueToCopy == null) { continue; } valueToCopy = valueToCopy.Trim(); if (valueToCopy.Length > 0) { if ((checkForZero && !valueToCopy.Equals("0")) || // Skip over non-zero values for counters (isFlag && !valueToCopy.Equals(Constant.BooleanValue.False, StringComparison.OrdinalIgnoreCase)) || // Skip over false values for flags (!checkForZero && !isFlag)) { indexToCopyFrom = previousIndex; // We found a non-empty value valueSource = file; break; } } } string newContent = valueToCopy; if (indexToCopyFrom < 0) { // Display a dialog box saying there is nothing to propagate. // Note that this should never be displayed, as the menu shouldn't be highlit if there is nothing to propagate // But just in case... Dialogs.DataEntryNothingToPropagateDialog(Application.Current.MainWindow); return; } // Display the appropriate dialog box that explains what will happen. Arguments indicate what is to be propagated and how many files will be affected int filesAffected = currentRowIndex - indexToCopyFrom; if (Dialogs.DataEntryConfirmPropagateFromLastValueDialog(Application.Current.MainWindow, valueToCopy, filesAffected) != true) { return; // operation cancelled // newContent = this.FileDatabase.FileTable[currentRowIndex].GetValueDisplayString(control.DataLabel); // No change, so return the current value } // Update the affected files. Note that we start on the row after the one with a value in it to the current row. Mouse.OverrideCursor = Cursors.Wait; this.FileDatabase.UpdateFiles(valueSource, control.DataLabel, indexToCopyFrom + 1, currentRowIndex); control.SetContentAndTooltip(newContent); Mouse.OverrideCursor = null; }
// Create the Context menu, incluidng settings its callbakcs private void SetContextMenuCallbacks(DataEntryControl control) { if (Util.GlobalReferences.TimelapseState.IsViewOnly) { return; } // Start with an empty clipboard // Its in a try / catch as one user reported an unusual error: OpenClipboardFailed try { Clipboard.SetText(String.Empty); } catch { System.Diagnostics.Debug.Print("Error in setting text in clipboard (see SetContextMenuCallbacks in DataEntryHandler"); } MenuItem menuItemPropagateFromLastValue = new MenuItem() { IsCheckable = false, Tag = control, Header = "Propagate from the last non-empty value to here" }; if (control is DataEntryCounter) { menuItemPropagateFromLastValue.Header = "Propagate from the last non-zero value to here"; } menuItemPropagateFromLastValue.Click += this.MenuItemPropagateFromLastValue_Click; MenuItem menuItemCopyForward = new MenuItem() { IsCheckable = false, Header = "Copy forward to end", ToolTip = "The value of this field will be copied forward from this file to the last file in this set", Tag = control }; menuItemCopyForward.Click += this.MenuItemPropagateForward_Click; MenuItem menuItemCopyCurrentValue = new MenuItem() { IsCheckable = false, Header = "Copy to all", Tag = control }; menuItemCopyCurrentValue.Click += this.MenuItemCopyCurrentValueToAll_Click; MenuItem menuItemCopy = new MenuItem() { IsCheckable = false, Header = "Copy", ToolTip = "Copy will copy this field's entire content to the clipboard", Tag = control }; menuItemCopy.Click += this.MenuItemCopyToClipboard_Click; MenuItem menuItemPaste = new MenuItem() { IsCheckable = false, Header = "Paste", ToolTip = "Paste will replace this field's content with the clipboard's content", Tag = control }; menuItemPaste.Click += this.MenuItemPasteFromClipboard_Click; // DataEntrHandler.PropagateFromLastValueIndex and CopyForwardIndex must be kept in sync with the add order here ContextMenu menu = new ContextMenu(); menu.Items.Add(menuItemPropagateFromLastValue); menu.Items.Add(menuItemCopyForward); menu.Items.Add(menuItemCopyCurrentValue); Separator menuSeparator = new Separator(); menu.Items.Add(menuSeparator); menu.Items.Add(menuItemCopy); menu.Items.Add(menuItemPaste); control.Container.ContextMenu = menu; control.Container.PreviewMouseRightButtonDown += this.Container_PreviewMouseRightButtonDown; // For the File/Folder/RelativePath controls, all which are read only, hide the irrelevant menu items. // This could be made more efficient by simply not creating those items, but given the low case we just left it as is. if (control.DataLabel == Constant.DatabaseColumn.File || control.DataLabel == Constant.DatabaseColumn.Folder || control.DataLabel == Constant.DatabaseColumn.RelativePath) { if (control is DataEntryNote note) { note.ContentControl.ContextMenu = menu; menuItemPropagateFromLastValue.Visibility = Visibility.Collapsed; menuItemCopyForward.Visibility = Visibility.Collapsed; menuItemCopyCurrentValue.Visibility = Visibility.Collapsed; menuSeparator.Visibility = Visibility.Collapsed; if (control.ContentReadOnly) { menuItemPaste.Visibility = Visibility.Collapsed; } } } else if (control is DataEntryCounter counter) { counter.ContentControl.ContextMenu = menu; } else if (control is DataEntryNote note) { note.ContentControl.ContextMenu = menu; } else if (control is DataEntryChoice choice) { choice.ContentControl.ContextMenu = menu; } else if (control is DataEntryFlag flag) { flag.ContentControl.ContextMenu = menu; } else { throw new NotSupportedException(String.Format("Unhandled control type {0}.", control.GetType().Name)); } }