Add setting to persist ProductsGrid column widths

This commit is contained in:
Michael Bucari-Tovo 2022-05-09 15:30:18 -06:00
parent cfe2eac351
commit 30feb42ed8
5 changed files with 240 additions and 231 deletions

View File

@ -127,13 +127,13 @@ namespace AppScaffolding
config.AutoScan = true; config.AutoScan = true;
if (!config.Exists(nameof(config.HiddenGridColumns))) if (!config.Exists(nameof(config.HiddenGridColumns)))
config.HiddenGridColumns = Array.Empty<string>(); config.HiddenGridColumns = new Dictionary<string, bool>();
if (!config.Exists(nameof(config.GridColumnsDisplayIndices))) if (!config.Exists(nameof(config.GridColumnsDisplayIndices)))
{ config.GridColumnsDisplayIndices = new Dictionary<string, int>();
int startIndex = 0;
config.GridColumnsDisplayIndices = new int[30].Select(_ => startIndex++).ToArray(); if (!config.Exists(nameof(config.GridColumnsWidths)))
} config.GridColumnsWidths = new Dictionary<string, int>();
} }
/// <summary>Initialize logging. Run after migration</summary> /// <summary>Initialize logging. Run after migration</summary>

View File

@ -7,237 +7,236 @@ using Newtonsoft.Json.Linq;
namespace FileManager namespace FileManager
{ {
public class PersistentDictionary public class PersistentDictionary
{ {
public string Filepath { get; } public string Filepath { get; }
public bool IsReadOnly { get; } public bool IsReadOnly { get; }
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else // optimize for strings. expectation is most settings will be strings and a rare exception will be something else
private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>(); private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>();
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>(); private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
public PersistentDictionary(string filepath, bool isReadOnly = false) public PersistentDictionary(string filepath, bool isReadOnly = false)
{ {
Filepath = filepath; Filepath = filepath;
IsReadOnly = isReadOnly; IsReadOnly = isReadOnly;
if (File.Exists(Filepath)) if (File.Exists(Filepath))
return; return;
// will create any missing directories, incl subdirectories. if all already exist: no action // will create any missing directories, incl subdirectories. if all already exist: no action
Directory.CreateDirectory(Path.GetDirectoryName(filepath)); Directory.CreateDirectory(Path.GetDirectoryName(filepath));
if (IsReadOnly) if (IsReadOnly)
return; return;
createNewFile(); createNewFile();
} }
public string GetString(string propertyName) public string GetString(string propertyName)
{ {
if (!stringCache.ContainsKey(propertyName)) if (!stringCache.ContainsKey(propertyName))
{ {
var jObject = readFile(); var jObject = readFile();
if (!jObject.ContainsKey(propertyName)) if (!jObject.ContainsKey(propertyName))
return null; return null;
stringCache[propertyName] = jObject[propertyName].Value<string>(); stringCache[propertyName] = jObject[propertyName].Value<string>();
} }
return stringCache[propertyName]; return stringCache[propertyName];
} }
public T GetNonString<T>(string propertyName) public T GetNonString<T>(string propertyName)
{ {
var obj = GetObject(propertyName); var obj = GetObject(propertyName);
if (obj is null) return default; if (obj is null) return default;
if (obj is JValue jValue) return jValue.Value<T>(); if (obj is JValue jValue) return jValue.Value<T>();
if (obj is JObject jObject) return jObject.ToObject<T>(); if (obj is JObject jObject) return jObject.ToObject<T>();
if (obj is JArray jArray && typeof(T).IsArray) return jArray.ToObject<T>(); return (T)obj;
return (T)obj; }
}
public object GetObject(string propertyName) public object GetObject(string propertyName)
{ {
if (!objectCache.ContainsKey(propertyName)) if (!objectCache.ContainsKey(propertyName))
{ {
var jObject = readFile(); var jObject = readFile();
if (!jObject.ContainsKey(propertyName)) if (!jObject.ContainsKey(propertyName))
return null; return null;
objectCache[propertyName] = jObject[propertyName].Value<object>(); objectCache[propertyName] = jObject[propertyName].Value<object>();
} }
return objectCache[propertyName]; return objectCache[propertyName];
} }
public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}"); public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}");
public string GetStringFromJsonPath(string jsonPath) public string GetStringFromJsonPath(string jsonPath)
{ {
if (!stringCache.ContainsKey(jsonPath)) if (!stringCache.ContainsKey(jsonPath))
{ {
try try
{ {
var jObject = readFile(); var jObject = readFile();
var token = jObject.SelectToken(jsonPath); var token = jObject.SelectToken(jsonPath);
if (token is null) if (token is null)
return null; return null;
stringCache[jsonPath] = (string)token; stringCache[jsonPath] = (string)token;
} }
catch catch
{ {
return null; 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(); private object locker { get; } = new object();
public void SetString(string propertyName, string newValue) public void SetString(string propertyName, string newValue)
{ {
// only do this check in string cache, NOT object cache // only do this check in string cache, NOT object cache
if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue) if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue)
return; return;
// set cache // set cache
stringCache[propertyName] = newValue; stringCache[propertyName] = newValue;
writeFile(propertyName, newValue); writeFile(propertyName, newValue);
} }
public void SetNonString(string propertyName, object newValue) public void SetNonString(string propertyName, object newValue)
{ {
// set cache // set cache
objectCache[propertyName] = newValue; objectCache[propertyName] = newValue;
var parsedNewValue = JToken.Parse(JsonConvert.SerializeObject(newValue)); var parsedNewValue = JToken.Parse(JsonConvert.SerializeObject(newValue));
writeFile(propertyName, parsedNewValue); writeFile(propertyName, parsedNewValue);
} }
private void writeFile(string propertyName, JToken newValue) private void writeFile(string propertyName, JToken newValue)
{ {
if (IsReadOnly) if (IsReadOnly)
return; return;
// write new setting to file // write new setting to file
lock (locker) lock (locker)
{ {
var jObject = readFile(); var jObject = readFile();
var startContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); var startContents = JsonConvert.SerializeObject(jObject, Formatting.Indented);
jObject[propertyName] = newValue; jObject[propertyName] = newValue;
var endContents = JsonConvert.SerializeObject(jObject, Formatting.Indented); var endContents = JsonConvert.SerializeObject(jObject, Formatting.Indented);
if (startContents == endContents) if (startContents == endContents)
return; 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 /// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
{ /// <returns>Value was changed</returns>
var str = formatValueForLog(newValue?.ToString()); public bool SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { propertyName, newValue = str }); {
} if (IsReadOnly)
catch { } return false;
}
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary> var path = $"{jsonPath}.{propertyName}";
/// <returns>Value was changed</returns>
public bool SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
{
if (IsReadOnly)
return false;
var path = $"{jsonPath}.{propertyName}"; {
// only do this check in string cache, NOT object cache
if (stringCache.ContainsKey(path) && stringCache[path] == newValue)
return false;
{ // set cache
// only do this check in string cache, NOT object cache stringCache[path] = newValue;
if (stringCache.ContainsKey(path) && stringCache[path] == newValue) }
return false;
// set cache try
stringCache[path] = newValue; {
} lock (locker)
{
var jObject = readFile();
var token = jObject.SelectToken(jsonPath);
if (token is null || token[propertyName] is null)
return false;
try var oldValue = token.Value<string>(propertyName);
{ if (oldValue == newValue)
lock (locker) return false;
{
var jObject = readFile();
var token = jObject.SelectToken(jsonPath);
if (token is null || token[propertyName] is null)
return false;
var oldValue = token.Value<string>(propertyName); token[propertyName] = newValue;
if (oldValue == newValue) File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
return false; }
}
catch (Exception exDebug)
{
return false;
}
token[propertyName] = newValue; if (!suppressLogging)
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); {
} try
} {
catch (Exception exDebug) var str = formatValueForLog(newValue?.ToString());
{ Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { jsonPath, propertyName, newValue = str });
return false; }
} catch { }
}
if (!suppressLogging) return true;
{ }
try
{
var str = formatValueForLog(newValue?.ToString());
Serilog.Log.Logger.Information("Config changed. {@DebugInfo}", new { jsonPath, propertyName, newValue = str });
}
catch { }
}
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) private JObject readFile()
=> value is null ? "[null]" {
: string.IsNullOrEmpty(value) ? "[empty]" if (!File.Exists(Filepath))
: string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]" {
: value.Length > 100 ? $"[Length={value.Length}] {value[0..50]}...{value[^50..^0]}" var msg = "Unrecoverable error. Settings file cannot be found";
: value; var ex = new FileNotFoundException(msg, Filepath);
Serilog.Log.Logger.Error(ex, msg);
throw ex;
}
private JObject readFile() var settingsJsonContents = File.ReadAllText(Filepath);
{
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); if (string.IsNullOrWhiteSpace(settingsJsonContents))
{
createNewFile();
settingsJsonContents = File.ReadAllText(Filepath);
}
if (string.IsNullOrWhiteSpace(settingsJsonContents)) var jObject = JsonConvert.DeserializeObject<JObject>(settingsJsonContents);
{
createNewFile();
settingsJsonContents = File.ReadAllText(Filepath);
}
var jObject = JsonConvert.DeserializeObject<JObject>(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) return jObject;
{ }
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; private void createNewFile()
} {
File.WriteAllText(Filepath, "{}");
private void createNewFile() System.Threading.Thread.Sleep(100);
{ }
File.WriteAllText(Filepath, "{}"); }
System.Threading.Thread.Sleep(100);
}
}
} }

View File

@ -180,20 +180,27 @@ namespace LibationFileManager
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value); set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
} }
[Description("A list of GridView data property names whose columns should be hidden in ProductsGrid")] [Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
public string[] HiddenGridColumns public Dictionary<string, bool> HiddenGridColumns
{ {
get => persistentDictionary.GetNonString<string[]>(nameof(HiddenGridColumns)); get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(HiddenGridColumns));
set => persistentDictionary.SetNonString(nameof(HiddenGridColumns), value); set => persistentDictionary.SetNonString(nameof(HiddenGridColumns), value);
} }
[Description("A DisplayIndex list of columns in ProductsGrid")] [Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
public int[] GridColumnsDisplayIndices public Dictionary<string, int> GridColumnsDisplayIndices
{ {
get => persistentDictionary.GetNonString<int[]>(nameof(GridColumnsDisplayIndices)); get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsDisplayIndices));
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value); 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<string, int> GridColumnsWidths
{
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsWidths));
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
}
public enum BadBookAction public enum BadBookAction
{ {
[Description("Ask each time what action to take.")] [Description("Ask each time what action to take.")]

View File

@ -98,6 +98,7 @@
this.gridEntryDataGridView.Size = new System.Drawing.Size(1510, 380); this.gridEntryDataGridView.Size = new System.Drawing.Size(1510, 380);
this.gridEntryDataGridView.TabIndex = 0; this.gridEntryDataGridView.TabIndex = 0;
this.gridEntryDataGridView.ColumnDisplayIndexChanged += new System.Windows.Forms.DataGridViewColumnEventHandler(this.gridEntryDataGridView_ColumnDisplayIndexChanged); this.gridEntryDataGridView.ColumnDisplayIndexChanged += new System.Windows.Forms.DataGridViewColumnEventHandler(this.gridEntryDataGridView_ColumnDisplayIndexChanged);
this.gridEntryDataGridView.ColumnWidthChanged += new System.Windows.Forms.DataGridViewColumnEventHandler(this.gridEntryDataGridView_ColumnWidthChanged);
// //
// dataGridViewImageButtonBoxColumn1 // dataGridViewImageButtonBoxColumn1
// //

View File

@ -34,7 +34,7 @@ namespace LibationWinForms
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
// alias // alias
private DataGridView _dataGridView => gridEntryDataGridView; private DataGridView _dataGridView => gridEntryDataGridView;
public ProductsGrid() public ProductsGrid()
{ {
@ -238,16 +238,16 @@ namespace LibationWinForms
//Restore Grid Display Settings //Restore Grid Display Settings
var config = Configuration.Instance; var config = Configuration.Instance;
var displayIndices = config.GridColumnsDisplayIndices;
var hiddenGridColumns = config.HiddenGridColumns; var hiddenGridColumns = config.HiddenGridColumns;
var displayIndices = config.GridColumnsDisplayIndices;
var gridColumnsWidths = config.GridColumnsWidths;
var cmsKiller = new ContextMenuStrip(); var cmsKiller = new ContextMenuStrip();
int columnIndex = 0;
foreach (DataGridViewColumn column in _dataGridView.Columns) foreach (DataGridViewColumn column in _dataGridView.Columns)
{ {
var visible = !hiddenGridColumns.Contains(column.DataPropertyName);
var itemName = column.DataPropertyName; var itemName = column.DataPropertyName;
var visible = !hiddenGridColumns.GetValueOrDefault(itemName, false);
var menuItem = new ToolStripMenuItem() var menuItem = new ToolStripMenuItem()
{ {
@ -259,7 +259,9 @@ namespace LibationWinForms
contextMenuStrip1.Items.Add(menuItem); contextMenuStrip1.Items.Add(menuItem);
column.Visible = visible; 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; column.HeaderCell.ContextMenuStrip = contextMenuStrip1;
//Setting a default ContextMenuStrip will allow the columns to handle the //Setting a default ContextMenuStrip will allow the columns to handle the
@ -272,6 +274,24 @@ namespace LibationWinForms
base.OnVisibleChanged(e); 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) private void HideMenuItem_Click(object sender, EventArgs e)
{ {
var menuItem = sender as ToolStripMenuItem; var menuItem = sender as ToolStripMenuItem;
@ -287,32 +307,14 @@ namespace LibationWinForms
menuItem.Checked = !visible; menuItem.Checked = !visible;
column.Visible = !visible; column.Visible = !visible;
Configuration.Instance.HiddenGridColumns = var config = Configuration.Instance;
_dataGridView.Columns
.Cast<DataGridViewColumn>()
.Where(c => !c.Visible)
.Select(c => c.DataPropertyName)
.ToArray();
}
}
private void gridEntryDataGridView_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e) var dictionary = config.HiddenGridColumns;
{ dictionary[propertyName] = visible;
Configuration.Instance.GridColumnsDisplayIndices config.HiddenGridColumns = dictionary;
= _dataGridView.Columns }
.Cast<DataGridViewColumn>()
.Select(c => c.DisplayIndex)
.ToArray();
} }
#endregion #endregion
} }
class ContextMenuStripEx : ContextMenuStrip
{
protected override ToolStripItem CreateDefaultItem(string text, Image image, EventHandler onClick)
{
return base.CreateDefaultItem(text, image, onClick);
}
}
} }