From 30feb42ed8081364b572fcc99228ac6cdab6451a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 May 2022 15:30:18 -0600 Subject: [PATCH] Add setting to persist ProductsGrid column widths --- Source/AppScaffolding/LibationScaffolding.cs | 10 +- Source/FileManager/PersistentDictionary.cs | 383 +++++++++--------- Source/LibationFileManager/Configuration.cs | 19 +- .../grid/ProductsGrid.Designer.cs | 1 + Source/LibationWinForms/grid/ProductsGrid.cs | 58 +-- 5 files changed, 240 insertions(+), 231 deletions(-) diff --git a/Source/AppScaffolding/LibationScaffolding.cs b/Source/AppScaffolding/LibationScaffolding.cs index 493b5964..21daf970 100644 --- a/Source/AppScaffolding/LibationScaffolding.cs +++ b/Source/AppScaffolding/LibationScaffolding.cs @@ -127,13 +127,13 @@ namespace AppScaffolding config.AutoScan = true; if (!config.Exists(nameof(config.HiddenGridColumns))) - config.HiddenGridColumns = Array.Empty(); + config.HiddenGridColumns = new Dictionary(); if (!config.Exists(nameof(config.GridColumnsDisplayIndices))) - { - int startIndex = 0; - config.GridColumnsDisplayIndices = new int[30].Select(_ => startIndex++).ToArray(); - } + config.GridColumnsDisplayIndices = new Dictionary(); + + if (!config.Exists(nameof(config.GridColumnsWidths))) + config.GridColumnsWidths = new Dictionary(); } /// Initialize logging. Run after migration diff --git a/Source/FileManager/PersistentDictionary.cs b/Source/FileManager/PersistentDictionary.cs index 0073a705..53e87c9a 100644 --- a/Source/FileManager/PersistentDictionary.cs +++ b/Source/FileManager/PersistentDictionary.cs @@ -7,237 +7,236 @@ using Newtonsoft.Json.Linq; namespace FileManager { - public class PersistentDictionary - { - public string Filepath { get; } - public bool IsReadOnly { get; } + public class PersistentDictionary + { + public string Filepath { get; } + public bool IsReadOnly { get; } - // optimize for strings. expectation is most settings will be strings and a rare exception will be something else - private Dictionary stringCache { get; } = new Dictionary(); - private Dictionary objectCache { get; } = new Dictionary(); + // optimize for strings. expectation is most settings will be strings and a rare exception will be something else + private Dictionary stringCache { get; } = new Dictionary(); + private Dictionary objectCache { get; } = new Dictionary(); - public PersistentDictionary(string filepath, bool isReadOnly = false) - { - Filepath = filepath; - IsReadOnly = isReadOnly; + public PersistentDictionary(string filepath, bool isReadOnly = false) + { + Filepath = filepath; + IsReadOnly = isReadOnly; - if (File.Exists(Filepath)) - return; + if (File.Exists(Filepath)) + return; - // will create any missing directories, incl subdirectories. if all already exist: no action - Directory.CreateDirectory(Path.GetDirectoryName(filepath)); + // will create any missing directories, incl subdirectories. if all already exist: no action + Directory.CreateDirectory(Path.GetDirectoryName(filepath)); - if (IsReadOnly) - return; + if (IsReadOnly) + return; - createNewFile(); - } + createNewFile(); + } - public string GetString(string propertyName) - { - if (!stringCache.ContainsKey(propertyName)) - { - var jObject = readFile(); - if (!jObject.ContainsKey(propertyName)) - return null; - stringCache[propertyName] = jObject[propertyName].Value(); - } + public string GetString(string propertyName) + { + if (!stringCache.ContainsKey(propertyName)) + { + var jObject = readFile(); + if (!jObject.ContainsKey(propertyName)) + return null; + stringCache[propertyName] = jObject[propertyName].Value(); + } - return stringCache[propertyName]; - } + return stringCache[propertyName]; + } - public T GetNonString(string propertyName) - { - var obj = GetObject(propertyName); - if (obj is null) return default; - if (obj is JValue jValue) return jValue.Value(); - if (obj is JObject jObject) return jObject.ToObject(); - if (obj is JArray jArray && typeof(T).IsArray) return jArray.ToObject(); - return (T)obj; - } + public T GetNonString(string propertyName) + { + var obj = GetObject(propertyName); + if (obj is null) return default; + if (obj is JValue jValue) return jValue.Value(); + if (obj is JObject jObject) return jObject.ToObject(); + return (T)obj; + } - public object GetObject(string propertyName) - { - if (!objectCache.ContainsKey(propertyName)) - { - var jObject = readFile(); - if (!jObject.ContainsKey(propertyName)) - return null; - objectCache[propertyName] = jObject[propertyName].Value(); - } + public object GetObject(string propertyName) + { + if (!objectCache.ContainsKey(propertyName)) + { + var jObject = readFile(); + if (!jObject.ContainsKey(propertyName)) + return null; + objectCache[propertyName] = jObject[propertyName].Value(); + } - return objectCache[propertyName]; - } + return objectCache[propertyName]; + } - public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}"); - public string GetStringFromJsonPath(string jsonPath) - { - if (!stringCache.ContainsKey(jsonPath)) - { - try - { - var jObject = readFile(); - var token = jObject.SelectToken(jsonPath); - if (token is null) - return null; - stringCache[jsonPath] = (string)token; - } - catch - { - return null; - } - } + public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}"); + public string GetStringFromJsonPath(string jsonPath) + { + if (!stringCache.ContainsKey(jsonPath)) + { + try + { + var jObject = readFile(); + var token = jObject.SelectToken(jsonPath); + if (token is null) + return null; + stringCache[jsonPath] = (string)token; + } + catch + { + return null; + } + } - return stringCache[jsonPath]; - } + return stringCache[jsonPath]; + } - public bool Exists(string propertyName) => readFile().ContainsKey(propertyName); + public bool Exists(string propertyName) => readFile().ContainsKey(propertyName); - private object locker { get; } = new object(); - public void SetString(string propertyName, string newValue) - { - // only do this check in string cache, NOT object cache - if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue) - return; + private object locker { get; } = new object(); + public void SetString(string propertyName, string newValue) + { + // only do this check in string cache, NOT object cache + if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue) + return; - // set cache - stringCache[propertyName] = newValue; + // set cache + stringCache[propertyName] = newValue; - writeFile(propertyName, newValue); - } + writeFile(propertyName, newValue); + } - public void SetNonString(string propertyName, object newValue) - { - // set cache - objectCache[propertyName] = newValue; + public void SetNonString(string propertyName, object newValue) + { + // set cache + objectCache[propertyName] = newValue; - var parsedNewValue = JToken.Parse(JsonConvert.SerializeObject(newValue)); - writeFile(propertyName, parsedNewValue); - } + var parsedNewValue = JToken.Parse(JsonConvert.SerializeObject(newValue)); + writeFile(propertyName, parsedNewValue); + } - private void writeFile(string propertyName, JToken newValue) - { - if (IsReadOnly) - return; + private void writeFile(string propertyName, JToken newValue) + { + if (IsReadOnly) + return; - // write new setting to file - lock (locker) - { - var jObject = readFile(); - var startContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); + // write new setting to file + lock (locker) + { + var jObject = readFile(); + var startContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); - jObject[propertyName] = newValue; - var endContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); + jObject[propertyName] = newValue; + var endContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); - if (startContents == endContents) - return; + if (startContents == endContents) + return; + + File.WriteAllText(Filepath, endContents); + } - File.WriteAllText(Filepath, endContents); - } + try + { + var str = formatValueForLog(newValue?.ToString()); + Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { propertyName, newValue = str }); + } + catch { } + } - try - { - var str = formatValueForLog(newValue?.ToString()); - Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { propertyName, newValue = str }); - } - catch { } - } + /// WILL ONLY set if already present. WILL NOT create new + /// Value was changed + public bool SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false) + { + if (IsReadOnly) + return false; - /// WILL ONLY set if already present. WILL NOT create new - /// Value was changed - public bool SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false) - { - if (IsReadOnly) - return false; + var path = $"{jsonPath}.{propertyName}"; - var path = $"{jsonPath}.{propertyName}"; + { + // only do this check in string cache, NOT object cache + if (stringCache.ContainsKey(path) && stringCache[path] == newValue) + return false; - { - // only do this check in string cache, NOT object cache - if (stringCache.ContainsKey(path) && stringCache[path] == newValue) - return false; + // set cache + stringCache[path] = newValue; + } - // set cache - stringCache[path] = newValue; - } + try + { + lock (locker) + { + var jObject = readFile(); + var token = jObject.SelectToken(jsonPath); + if (token is null || token[propertyName] is null) + return false; - try - { - lock (locker) - { - var jObject = readFile(); - var token = jObject.SelectToken(jsonPath); - if (token is null || token[propertyName] is null) - return false; + var oldValue = token.Value(propertyName); + if (oldValue == newValue) + return false; - var oldValue = token.Value(propertyName); - if (oldValue == newValue) - return false; + token[propertyName] = newValue; + File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); + } + } + catch (Exception exDebug) + { + return false; + } - token[propertyName] = newValue; - File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); - } - } - catch (Exception exDebug) - { - return false; - } + if (!suppressLogging) + { + try + { + var str = formatValueForLog(newValue?.ToString()); + Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { jsonPath, propertyName, newValue = str }); + } + catch { } + } - if (!suppressLogging) - { - try - { - var str = formatValueForLog(newValue?.ToString()); - Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { jsonPath, propertyName, newValue = str }); - } - catch { } - } + return true; + } - return true; - } + private static string formatValueForLog(string value) + => value is null ? "[null]" + : string.IsNullOrEmpty(value) ? "[empty]" + : string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]" + : value.Length > 100 ? $"[Length={value.Length}] {value[0..50]}...{value[^50..^0]}" + : value; - private static string formatValueForLog(string value) - => value is null ? "[null]" - : string.IsNullOrEmpty(value) ? "[empty]" - : string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]" - : value.Length > 100 ? $"[Length={value.Length}] {value[0..50]}...{value[^50..^0]}" - : value; + private JObject readFile() + { + if (!File.Exists(Filepath)) + { + var msg = "Unrecoverable error. Settings file cannot be found"; + var ex = new FileNotFoundException(msg, Filepath); + Serilog.Log.Logger.Error(ex, msg); + throw ex; + } - private JObject readFile() - { - if (!File.Exists(Filepath)) - { - var msg = "Unrecoverable error. Settings file cannot be found"; - var ex = new FileNotFoundException(msg, Filepath); - Serilog.Log.Logger.Error(ex, msg); - throw ex; - } + var settingsJsonContents = File.ReadAllText(Filepath); - var settingsJsonContents = File.ReadAllText(Filepath); + if (string.IsNullOrWhiteSpace(settingsJsonContents)) + { + createNewFile(); + settingsJsonContents = File.ReadAllText(Filepath); + } - if (string.IsNullOrWhiteSpace(settingsJsonContents)) - { - createNewFile(); - settingsJsonContents = File.ReadAllText(Filepath); - } + var jObject = JsonConvert.DeserializeObject(settingsJsonContents); - var jObject = JsonConvert.DeserializeObject(settingsJsonContents); + if (jObject is null) + { + var msg = "Unrecoverable error. Unable to read settings from Settings file"; + var ex = new NullReferenceException(msg); + Serilog.Log.Logger.Error(ex, msg); + throw ex; + } - if (jObject is null) - { - var msg = "Unrecoverable error. Unable to read settings from Settings file"; - var ex = new NullReferenceException(msg); - Serilog.Log.Logger.Error(ex, msg); - throw ex; - } + return jObject; + } - return jObject; - } - - private void createNewFile() - { - File.WriteAllText(Filepath, "{}"); - System.Threading.Thread.Sleep(100); - } - } + private void createNewFile() + { + File.WriteAllText(Filepath, "{}"); + System.Threading.Thread.Sleep(100); + } + } } diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs index c9610fb1..874f49ab 100644 --- a/Source/LibationFileManager/Configuration.cs +++ b/Source/LibationFileManager/Configuration.cs @@ -180,20 +180,27 @@ namespace LibationFileManager set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value); } - [Description("A list of GridView data property names whose columns should be hidden in ProductsGrid")] - public string[] HiddenGridColumns + [Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")] + public Dictionary HiddenGridColumns { - get => persistentDictionary.GetNonString(nameof(HiddenGridColumns)); + get => persistentDictionary.GetNonString>(nameof(HiddenGridColumns)); set => persistentDictionary.SetNonString(nameof(HiddenGridColumns), value); } - [Description("A DisplayIndex list of columns in ProductsGrid")] - public int[] GridColumnsDisplayIndices + [Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")] + public Dictionary GridColumnsDisplayIndices { - get => persistentDictionary.GetNonString(nameof(GridColumnsDisplayIndices)); + get => persistentDictionary.GetNonString>(nameof(GridColumnsDisplayIndices)); set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value); } + [Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")] + public Dictionary GridColumnsWidths + { + get => persistentDictionary.GetNonString>(nameof(GridColumnsWidths)); + set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value); + } + public enum BadBookAction { [Description("Ask each time what action to take.")] diff --git a/Source/LibationWinForms/grid/ProductsGrid.Designer.cs b/Source/LibationWinForms/grid/ProductsGrid.Designer.cs index fa993918..202fc078 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.Designer.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.Designer.cs @@ -98,6 +98,7 @@ this.gridEntryDataGridView.Size = new System.Drawing.Size(1510, 380); this.gridEntryDataGridView.TabIndex = 0; this.gridEntryDataGridView.ColumnDisplayIndexChanged += new System.Windows.Forms.DataGridViewColumnEventHandler(this.gridEntryDataGridView_ColumnDisplayIndexChanged); + this.gridEntryDataGridView.ColumnWidthChanged += new System.Windows.Forms.DataGridViewColumnEventHandler(this.gridEntryDataGridView_ColumnWidthChanged); // // dataGridViewImageButtonBoxColumn1 // diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index a828ed81..f28d0449 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -34,7 +34,7 @@ namespace LibationWinForms public event EventHandler VisibleCountChanged; // alias - private DataGridView _dataGridView => gridEntryDataGridView; + private DataGridView _dataGridView => gridEntryDataGridView; public ProductsGrid() { @@ -238,16 +238,16 @@ namespace LibationWinForms //Restore Grid Display Settings var config = Configuration.Instance; - var displayIndices = config.GridColumnsDisplayIndices; var hiddenGridColumns = config.HiddenGridColumns; + var displayIndices = config.GridColumnsDisplayIndices; + var gridColumnsWidths = config.GridColumnsWidths; var cmsKiller = new ContextMenuStrip(); - int columnIndex = 0; foreach (DataGridViewColumn column in _dataGridView.Columns) { - var visible = !hiddenGridColumns.Contains(column.DataPropertyName); var itemName = column.DataPropertyName; + var visible = !hiddenGridColumns.GetValueOrDefault(itemName, false); var menuItem = new ToolStripMenuItem() { @@ -259,7 +259,9 @@ namespace LibationWinForms contextMenuStrip1.Items.Add(menuItem); column.Visible = visible; - column.DisplayIndex = displayIndices[columnIndex++]; + column.DisplayIndex = displayIndices.GetValueOrDefault(itemName, column.Index); + column.Width = gridColumnsWidths.GetValueOrDefault(itemName, column.Width); + column.MinimumWidth = 10; column.HeaderCell.ContextMenuStrip = contextMenuStrip1; //Setting a default ContextMenuStrip will allow the columns to handle the @@ -272,6 +274,24 @@ namespace LibationWinForms base.OnVisibleChanged(e); } + private void gridEntryDataGridView_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e) + { + var config = Configuration.Instance; + + var dictionary = config.GridColumnsDisplayIndices; + dictionary[e.Column.DataPropertyName] = e.Column.DisplayIndex; + config.GridColumnsDisplayIndices = dictionary; + } + + private void gridEntryDataGridView_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) + { + var config = Configuration.Instance; + + var dictionary = config.GridColumnsWidths; + dictionary[e.Column.DataPropertyName] = e.Column.Width; + config.GridColumnsWidths = dictionary; + } + private void HideMenuItem_Click(object sender, EventArgs e) { var menuItem = sender as ToolStripMenuItem; @@ -287,32 +307,14 @@ namespace LibationWinForms menuItem.Checked = !visible; column.Visible = !visible; - Configuration.Instance.HiddenGridColumns = - _dataGridView.Columns - .Cast() - .Where(c => !c.Visible) - .Select(c => c.DataPropertyName) - .ToArray(); - } - } + var config = Configuration.Instance; - private void gridEntryDataGridView_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e) - { - Configuration.Instance.GridColumnsDisplayIndices - = _dataGridView.Columns - .Cast() - .Select(c => c.DisplayIndex) - .ToArray(); + var dictionary = config.HiddenGridColumns; + dictionary[propertyName] = visible; + config.HiddenGridColumns = dictionary; + } } #endregion } - - class ContextMenuStripEx : ContextMenuStrip - { - protected override ToolStripItem CreateDefaultItem(string text, Image image, EventHandler onClick) - { - return base.CreateDefaultItem(text, image, onClick); - } - } }