Save and restore grid column widths

Now preserving column widths for the three DataGrids and the main
ListView.  In theory the various grids would conveniently auto-size
to the content, but in practice that doesn't work well with
virtualization.

There is, of course, no simple "the width has changed" event
provided by the control.  On the plus side, you can attach a
property-change event handler to pretty much anything, so once you
know the trick it's possible to make everything work.  Yay WPF.
This commit is contained in:
Andy McFadden 2019-06-20 15:10:35 -07:00
parent 96a92f0335
commit ea6125ea82
5 changed files with 159 additions and 61 deletions

View File

@ -215,10 +215,11 @@ namespace CommonUtil {
}
/// <summary>
/// Deserializes an integer array from a string.
/// Deserializes an integer array from a string. Throws an exception if the format
/// is incorrect.
/// </summary>
/// <param name="cereal"></param>
/// <returns></returns>
/// <param name="cereal">Serialized data.</param>
/// <returns>Integer array with contents.</returns>
public static int[] DeserializeIntArray(string cereal) {
string[] splitted = cereal.Split(',');
if (splitted.Length == 0) {

View File

@ -1,15 +1,16 @@
/*
* This comes from "David Rickard's Tech Blog", posted by RandomEngy on March 8 2010:
* This comes from a blog entry, posted by RandomEngy on March 8 2010:
* https://blogs.msdn.microsoft.com/davidrickard/2010/03/08/saving-window-size-and-location-in-wpf-and-winforms/
* (see https://stackoverflow.com/a/2406604/294248 for discussion)
*
* Saving and restoring a window's size and position can be tricky when there are multiple
* displays involved. This uses the Win32 system functions to do the job properly and
* consistently. (In theory.)
*
* The code works for WinForms (save on FormClosing, restore on Load, using the native handle
* from the Handle property) and WPF (use the Window extension methods in Closing and
* SourceInitialized). Besides convenience, it has the added benefit of being able to
* capture the non-maximized values for a maximized window.
* from the Handle property) and WPF (use the Window extension methods provided below, in
* Closing and SourceInitialized). Besides convenience, it has the added benefit of being able
* to capture the non-maximized values for a maximized window.
*/
using System;
@ -116,9 +117,11 @@ namespace CommonWPF {
// Extension methods for WPF.
//
// Call from Closing event. Returns XML string with placement info.
public static string GetPlacement(this Window window) {
return GetPlacement(new WindowInteropHelper(window).Handle);
}
// Call from SourceInitialized event, passing in string from GetPlacement().
public static void SetPlacement(this Window window, string placementXml) {
SetPlacement(new WindowInteropHelper(window).Handle, placementXml);
}

View File

@ -289,12 +289,7 @@ namespace SourceGenWPF {
AppSettings.Global.SetInt(AppSettings.MAIN_SYMBOLS_HEIGHT,
(int)mMainWin.SymbolsPanelHeight);
#if false
SerializeCodeListColumnWidths();
SerializeReferencesColumnWidths();
SerializeNotesColumnWidths();
SerializeSymbolColumnWidths();
#endif
mMainWin.CaptureColumnWidths();
string runtimeDataDir = RuntimeDataAccess.GetDirectory();
if (runtimeDataDir == null) {
@ -350,19 +345,7 @@ namespace SourceGenWPF {
mMainWin.SymbolsPanelHeight =
settings.GetInt(AppSettings.MAIN_SYMBOLS_HEIGHT, 400);
#if false
// Configure column widths.
string widthStr = settings.GetString(AppSettings.CDLV_COL_WIDTHS, null);
if (!string.IsNullOrEmpty(widthStr)) {
CodeListColumnWidths widths = CodeListColumnWidths.Deserialize(widthStr);
if (widths != null) {
SetCodeListHeaderWidths(widths);
}
}
DeserializeReferencesColumnWidths();
DeserializeNotesColumnWidths();
DeserializeSymbolColumnWidths();
#endif
mMainWin.RestoreColumnWidths();
}
/// <summary>

View File

@ -23,7 +23,7 @@ limitations under the License.
mc:Ignorable="d"
Title="6502bench SourceGen"
Icon="/SourceGenWPF;component/Res/SourceGenIcon.ico"
Width="1000" Height="600" MinWidth="800" MinHeight="500"
Width="1000" Height="700" MinWidth="800" MinHeight="500"
SourceInitialized="Window_SourceInitialized"
Loaded="Window_Loaded"
LocationChanged="Window_LocationChanged"
@ -241,11 +241,11 @@ limitations under the License.
DockPanel, so that LastChildFill will expand this to fill all available space. -->
<Grid Name="triptychGrid" DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="168" MinWidth="100"/>
<ColumnDefinition Width="256" MinWidth="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="187" MinWidth="100"/>
<ColumnDefinition Width="256" MinWidth="100"/>
</Grid.ColumnDefinitions>
@ -272,9 +272,9 @@ limitations under the License.
SelectionMode="Single"
MouseDoubleClick="ReferencesList_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="Offset" Binding="{Binding Offset}"/>
<DataGridTextColumn Header="Addr" Binding="{Binding Addr}"/>
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Offset" Width="53" Binding="{Binding Offset}"/>
<DataGridTextColumn Header="Addr" Width="53" Binding="{Binding Addr}"/>
<DataGridTextColumn Header="Type" Width="119" Binding="{Binding Type}"/>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
@ -292,8 +292,8 @@ limitations under the License.
SelectionMode="Single"
MouseDoubleClick="NotesList_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="Offset" Binding="{Binding Offset}"/>
<DataGridTextColumn Header="Note" Binding="{Binding Note}">
<DataGridTextColumn Header="Offset" Width="53" Binding="{Binding Offset}"/>
<DataGridTextColumn Header="Note" Width="400" Binding="{Binding Note}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<!-- The default highlight uses dark blue background with
@ -357,15 +357,24 @@ limitations under the License.
MouseDoubleClick="CodeListView_MouseDoubleClick">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Offset" DisplayMemberBinding="{Binding Offset}"/>
<GridViewColumn Header="Addr" CellTemplate="{StaticResource addrHighlightTemplate}"/>
<GridViewColumn Header="Bytes" DisplayMemberBinding="{Binding Bytes}"/>
<GridViewColumn Header="Flags" DisplayMemberBinding="{Binding Flags}"/>
<GridViewColumn Header="Attr" DisplayMemberBinding="{Binding Attr}"/>
<GridViewColumn Header="Label" CellTemplate="{StaticResource labelHighlightTemplate}"/>
<GridViewColumn Header="Opcode" DisplayMemberBinding="{Binding Opcode}"/>
<GridViewColumn Header="Operand" DisplayMemberBinding="{Binding Operand}"/>
<GridViewColumn Header="Comment" DisplayMemberBinding="{Binding Comment}"/>
<GridViewColumn Header="Offset" Width="58"
DisplayMemberBinding="{Binding Offset}"/>
<GridViewColumn Header="Addr" Width="58"
CellTemplate="{StaticResource addrHighlightTemplate}"/>
<GridViewColumn Header="Bytes" Width="104"
DisplayMemberBinding="{Binding Bytes}"/>
<GridViewColumn Header="Flags" Width="78"
DisplayMemberBinding="{Binding Flags}"/>
<GridViewColumn Header="Attr" Width="45"
DisplayMemberBinding="{Binding Attr}"/>
<GridViewColumn Header="Label" Width="91"
CellTemplate="{StaticResource labelHighlightTemplate}"/>
<GridViewColumn Header="Opcode" Width="45"
DisplayMemberBinding="{Binding Opcode}"/>
<GridViewColumn Header="Operand" Width="98"
DisplayMemberBinding="{Binding Operand}"/>
<GridViewColumn Header="Comment" Width="300"
DisplayMemberBinding="{Binding Comment}"/>
</GridView>
</ListView.View>
@ -419,9 +428,9 @@ limitations under the License.
Sorting="SymbolsList_Sorting"
MouseDoubleClick="SymbolsList_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Type" Width="36" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Value" Width="66" Binding="{Binding Value}"/>
<DataGridTextColumn Header="Name" Width="120" Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>

View File

@ -19,20 +19,15 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using CommonUtil;
using CommonWPF;
namespace SourceGenWPF.ProjWin {
@ -71,7 +66,8 @@ namespace SourceGenWPF.ProjWin {
CodeDisplayList = new DisplayList();
codeListView.ItemsSource = CodeDisplayList;
// https://dlaa.me/blog/post/9425496 to re-auto-size after data added
// https://dlaa.me/blog/post/9425496 to re-auto-size after data added (this may
// not work with virtual items)
mMainCtrl = new MainController(this);
@ -91,8 +87,29 @@ namespace SourceGenWPF.ProjWin {
heightDesc.AddValueChanged(leftPanel.RowDefinitions[0], GridSizeChanged);
heightDesc.AddValueChanged(rightPanel.RowDefinitions[0], GridSizeChanged);
//GridView gv = (GridView)codeListView.View;
//gv.Columns[0].Width = 50;
// Add events that fire when column headers change size. We want this for
// the DataGrids and the main ListView.
PropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(
DataGridColumn.ActualWidthProperty, typeof(DataGridColumn));
AddColumnWidthChangeCallback(pd, referencesGrid);
AddColumnWidthChangeCallback(pd, notesGrid);
AddColumnWidthChangeCallback(pd, symbolsGrid);
// Same for the ListView. cf. https://stackoverflow.com/a/56694219/294248
pd = DependencyPropertyDescriptor.FromProperty(
GridViewColumn.WidthProperty, typeof(GridViewColumn));
AddColumnWidthChangeCallback(pd, (GridView)codeListView.View);
}
private void AddColumnWidthChangeCallback(PropertyDescriptor pd, DataGrid dg) {
foreach (DataGridColumn col in dg.Columns) {
pd.AddValueChanged(col, ColumnWidthChanged);
}
}
private void AddColumnWidthChangeCallback(PropertyDescriptor pd, GridView gv) {
foreach (GridViewColumn col in gv.Columns) {
pd.AddValueChanged(col, ColumnWidthChanged);
}
}
private void AddMultiKeyGestures() {
@ -176,7 +193,6 @@ namespace SourceGenWPF.ProjWin {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool mShowCodeListView;
/// <summary>
/// Which panel are we showing, launchPanel or codeListView?
@ -191,6 +207,7 @@ namespace SourceGenWPF.ProjWin {
OnPropertyChanged("CodeListVisibility");
}
}
private bool mShowCodeListView;
/// <summary>
/// Returns the visibility status of the launch panel.
@ -260,19 +277,24 @@ namespace SourceGenWPF.ProjWin {
#region Window placement
//
// We record the location and size of the window, and the sizes of the panels, in
// the settings file. All we need to do here is note that something has changed.
// We record the location and size of the window, the sizes of the panels, and the
// widths of the various columns. These events may fire rapidly while the user is
// resizing them, so we just want to set a flag noting that a change has been made.
//
private void Window_LocationChanged(object sender, EventArgs e) {
Debug.WriteLine("Main window location changed");
//Debug.WriteLine("Main window location changed");
AppSettings.Global.Dirty = true;
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e) {
Debug.WriteLine("Main window size changed");
//Debug.WriteLine("Main window size changed");
AppSettings.Global.Dirty = true;
}
private void GridSizeChanged(object sender, EventArgs e) {
Debug.WriteLine("Splitter size change");
//Debug.WriteLine("Splitter size change");
AppSettings.Global.Dirty = true;
}
private void ColumnWidthChanged(object sender, EventArgs e) {
//Debug.WriteLine("Column width change " + sender);
AppSettings.Global.Dirty = true;
}
@ -311,6 +333,86 @@ namespace SourceGenWPF.ProjWin {
#endregion Window placement
/// <summary>
/// Grabs the widths of the columns of the various grids and saves them in the
/// global AppSettings.
/// </summary>
public void CaptureColumnWidths() {
string widthStr;
widthStr = CaptureColumnWidths((GridView)codeListView.View);
AppSettings.Global.SetString(AppSettings.CDLV_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(referencesGrid);
AppSettings.Global.SetString(AppSettings.REFWIN_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(notesGrid);
AppSettings.Global.SetString(AppSettings.NOTEWIN_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(symbolsGrid);
AppSettings.Global.SetString(AppSettings.SYMWIN_COL_WIDTHS, widthStr);
}
private string CaptureColumnWidths(GridView gv) {
int[] widths = new int[gv.Columns.Count];
for (int i = 0; i < gv.Columns.Count; i++) {
widths[i] = (int)Math.Round(gv.Columns[i].ActualWidth);
}
return TextUtil.SerializeIntArray(widths);
}
private string CaptureColumnWidths(DataGrid dg) {
int[] widths = new int[dg.Columns.Count];
for (int i = 0; i < dg.Columns.Count; i++) {
widths[i] = (int)Math.Round(dg.Columns[i].ActualWidth);
}
return TextUtil.SerializeIntArray(widths);
}
/// <summary>
/// Applies column widths from the global AppSettings to the various grids.
/// </summary>
public void RestoreColumnWidths() {
RestoreColumnWidths((GridView)codeListView.View,
AppSettings.Global.GetString(AppSettings.CDLV_COL_WIDTHS, null));
RestoreColumnWidths(referencesGrid,
AppSettings.Global.GetString(AppSettings.REFWIN_COL_WIDTHS, null));
RestoreColumnWidths(notesGrid,
AppSettings.Global.GetString(AppSettings.NOTEWIN_COL_WIDTHS, null));
RestoreColumnWidths(symbolsGrid,
AppSettings.Global.GetString(AppSettings.SYMWIN_COL_WIDTHS, null));
}
private void RestoreColumnWidths(GridView gv, string str) {
int[] widths = null;
try {
widths = TextUtil.DeserializeIntArray(str);
} catch (Exception ex) {
Debug.WriteLine("Unable to deserialize widths for GridView");
return;
}
if (widths.Length != gv.Columns.Count) {
Debug.WriteLine("Incorrect column count for GridView");
return;
}
for (int i = 0; i < widths.Length; i++) {
gv.Columns[i].Width = widths[i];
}
}
private void RestoreColumnWidths(DataGrid dg, string str) {
int[] widths = null;
try {
widths = TextUtil.DeserializeIntArray(str);
} catch (Exception ex) {
Debug.WriteLine("Unable to deserialize widths for " + dg.Name);
return;
}
if (widths.Length != dg.Columns.Count) {
Debug.WriteLine("Incorrect column count for " + dg.Name);
return;
}
for (int i = 0; i < widths.Length; i++) {
dg.Columns[i].Width = widths[i];
}
}
/// <summary>
/// Sets the focus on the code list.