From 7e0faf280079bfeda1eec64597b8589641c51291 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 28 Jun 2019 15:17:48 -0700 Subject: [PATCH] Rework column width input Took another swing at doing things WPF-style. Ended up with TextBox objects backed by integer fields, which worked pretty well but didn't get the min/max values. Set up validation instead, once I figured out how to make it update in real time. --- SourceGenWPF/WpfGui/EditAppSettings.xaml | 44 ++++- SourceGenWPF/WpfGui/EditAppSettings.xaml.cs | 195 +++++++++++++------- 2 files changed, 169 insertions(+), 70 deletions(-) diff --git a/SourceGenWPF/WpfGui/EditAppSettings.xaml b/SourceGenWPF/WpfGui/EditAppSettings.xaml index 782fb9c..95c5e8d 100644 --- a/SourceGenWPF/WpfGui/EditAppSettings.xaml +++ b/SourceGenWPF/WpfGui/EditAppSettings.xaml @@ -165,17 +165,45 @@ limitations under the License. HorizontalAlignment="Right" Margin="0,0,4,0"/> + TextChanged="AsmColWidthTextBox_TextChanged"> + + + + + + + + + TextChanged="AsmColWidthTextBox_TextChanged"> + + + + + + + + + TextChanged="AsmColWidthTextBox_TextChanged"> + + + + + + + + + TextChanged="AsmColWidthTextBox_TextChanged"> + + + + + + + + diff --git a/SourceGenWPF/WpfGui/EditAppSettings.xaml.cs b/SourceGenWPF/WpfGui/EditAppSettings.xaml.cs index 5defc9d..7cdc47e 100644 --- a/SourceGenWPF/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGenWPF/WpfGui/EditAppSettings.xaml.cs @@ -17,11 +17,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; -using System.Windows.Input; using Microsoft.Win32; using CommonUtil; @@ -448,44 +448,113 @@ namespace SourceGenWPF.WpfGui { #region Asm Config + public const int ASM_COL_MIN_WIDTH = 1; + public const int ASM_COL_MAX_WIDTH = 200; + + // + // Numeric input fields, bound directly to TextBox.Text. This performs the basic + // validation, but we also do our own so we can cap min/max. The custom validation + // rule seems to fire ahead of the field assignment, so if our rule fails we won't + // try to assign here at all. + // + // The validation approach seems to make the most sense here because we don't dim the + // Apply/OK buttons when invalid input is present. Instead we just set the width to + // the minimum value when validation fails. + // + // See also https://stackoverflow.com/a/44586784/294248 + // + private int mAsmLabelColWidth; + public int AsmLabelColWidth { + get { return mAsmLabelColWidth; } + set { + if (mAsmLabelColWidth != value) { + mAsmLabelColWidth = value; + OnPropertyChanged(); + IsDirty = true; + } + } + } + private int mAsmOpcodeColWidth; + public int AsmOpcodeColWidth { + get { return mAsmOpcodeColWidth; } + set { + if (mAsmOpcodeColWidth != value) { + mAsmOpcodeColWidth = value; + OnPropertyChanged(); + IsDirty = true; + } + } + } + private int mAsmOperandColWidth; + public int AsmOperandColWidth { + get { return mAsmOperandColWidth; } + set { + if (mAsmOperandColWidth != value) { + mAsmOperandColWidth = value; + OnPropertyChanged(); + IsDirty = true; + } + } + } + private int mAsmCommentColWidth; + public int AsmCommentColWidth { + get { return mAsmCommentColWidth; } + set { + if (mAsmCommentColWidth != value) { + mAsmCommentColWidth = value; + OnPropertyChanged(); + IsDirty = true; + } + } + } + + // checkboxes private bool mShowCycleCounts; public bool ShowCycleCounts { get { return mShowCycleCounts; } set { - mShowCycleCounts = value; - OnPropertyChanged(); - mSettings.SetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, value); - IsDirty = true; + if (mShowCycleCounts != value) { + mShowCycleCounts = value; + OnPropertyChanged(); + mSettings.SetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, value); + IsDirty = true; + } } } private bool mLongLabelNewLine; public bool LongLabelNewLine { get { return mLongLabelNewLine; } set { - mLongLabelNewLine = value; - OnPropertyChanged(); - mSettings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, value); - IsDirty = true; + if (mLongLabelNewLine != value) { + mLongLabelNewLine = value; + OnPropertyChanged(); + mSettings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, value); + IsDirty = true; + } } } private bool mAddIdentComment; public bool AddIdentComment { get { return mAddIdentComment; } set { - mAddIdentComment = value; - OnPropertyChanged(); - mSettings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, value); - IsDirty = true; + if (mAddIdentComment != value) { + mAddIdentComment = value; + OnPropertyChanged(); + mSettings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, value); + IsDirty = true; + } } } private bool mDisableLabelLocalization; public bool DisableLabelLocalization { get { return mDisableLabelLocalization; } set { - mDisableLabelLocalization = value; - OnPropertyChanged(); - mSettings.SetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, value); - IsDirty = true; + if (mDisableLabelLocalization != value) { + mDisableLabelLocalization = value; + OnPropertyChanged(); + mSettings.SetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, value); + IsDirty = true; + } } } @@ -535,36 +604,19 @@ namespace SourceGenWPF.WpfGui { } /// - /// Checks whether the character typed into a column width entry field is allowed. - /// This is only useful for screening the character set, not the field contents. - /// - /// - /// This just screens the character. Doesn't handle selection operations like pasting. - /// Also, doesn't fire when you hit the space bar. This is only slightly better than - /// useless, but since we don't otherwise give an indication of wrongness it's nice - /// to have. - /// - /// Another approach is to bind Text to an integer property. This enables the validation - /// mechanism, which puts a red box around the field when it contains bad things, but - /// only after focus leaves the field. - /// - /// See also https://stackoverflow.com/q/1268552/294248 - /// - private void CheckWidthInput(object sender, TextCompositionEventArgs e) { - // Set e.Handled to true if the character is invalid. - char ch = e.Text[0]; - e.Handled = (ch < '0' || ch > '9'); - } - - /// - /// Updates the assembler config settings whenever one of the text fields is edited. + /// Updates the assembler config settings whenever one of the width text fields is edited. /// /// /// This fires 4x every time the combo box selection changes, as the new fields are - /// populated. That should work out correctly. + /// populated. That means we'll have incorrect intermediate states, but should + /// finish up correctly. /// - private void AsmLabelColWidthTextBox_TextChanged(object sender, TextChangedEventArgs e) { + private void AsmColWidthTextBox_TextChanged(object sender, TextChangedEventArgs e) { AssemblerInfo asm = (AssemblerInfo)asmConfigComboBox.SelectedItem; + if (asm == null) { + // fires during dialog initialization, before anything is selected + return; + } AssemblerConfig.SetConfig(mSettings, asm.AssemblerId, GetAsmConfigFromUi()); IsDirty = true; } @@ -574,31 +626,27 @@ namespace SourceGenWPF.WpfGui { /// /// private AssemblerConfig GetAsmConfigFromUi() { - const int MIN_WIDTH = 1; - const int MAX_WIDTH = 200; - - int[] widths = new int[4]; - for (int i = 0; i < widths.Length; i++) { - widths[i] = MIN_WIDTH; - } - int result; - if (int.TryParse(asmLabelColWidthTextBox.Text, out result) && result >= MIN_WIDTH && - result <= MAX_WIDTH) { - widths[0] = result; + if (!Validation.GetHasError(asmLabelColWidthTextBox)) { + widths[0] = AsmLabelColWidth; + } else { + widths[0] = ASM_COL_MIN_WIDTH; } - if (int.TryParse(asmOpcodeColWidthTextBox.Text, out result) && result >= MIN_WIDTH && - result <= MAX_WIDTH) { - widths[1] = result; + if (!Validation.GetHasError(asmOpcodeColWidthTextBox)) { + widths[1] = AsmOpcodeColWidth; + } else { + widths[1] = ASM_COL_MIN_WIDTH; } - if (int.TryParse(asmOperandColWidthTextBox.Text, out result) && result >= MIN_WIDTH && - result <= MAX_WIDTH) { - widths[2] = result; + if (!Validation.GetHasError(asmOperandColWidthTextBox)) { + widths[2] = AsmOperandColWidth; + } else { + widths[2] = ASM_COL_MIN_WIDTH; } - if (int.TryParse(asmCommentColWidthTextBox.Text, out result) && result >= MIN_WIDTH && - result <= MAX_WIDTH) { - widths[3] = result; + if (!Validation.GetHasError(asmCommentColWidthTextBox)) { + widths[3] = AsmCommentColWidth; + } else { + widths[3] = ASM_COL_MIN_WIDTH; } return new AssemblerConfig(asmExePathTextBox.Text, widths); @@ -691,4 +739,27 @@ namespace SourceGenWPF.WpfGui { #endregion PseudoOp } + + public class AsmColWidthRule : ValidationRule { + public override ValidationResult Validate(object value, CultureInfo cultureInfo) { + // Validating TextBox input, so value should always be a string. Check anyway. + string strValue = Convert.ToString(value); + if (string.IsNullOrEmpty(strValue)) { + //Debug.WriteLine("VVV not string"); + return new ValidationResult(false, "Could not convert to string"); + } + + if (int.TryParse(strValue, out int result)) { + if (result >= EditAppSettings.ASM_COL_MIN_WIDTH && + result <= EditAppSettings.ASM_COL_MAX_WIDTH) { + return ValidationResult.ValidResult; + } + //Debug.WriteLine("VVV out of range: '" + strValue + "' (" + result + ")"); + return new ValidationResult(false, "Column width out of range"); + } + + //Debug.WriteLine("VVV not valid integer: '" + strValue + "'"); + return new ValidationResult(false, "Invalid integer value: '" + strValue + "'"); + } + } }