From b45382d294ea5ecc3e8851e96b1d5c59df49820d Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Tue, 11 Jun 2019 18:45:08 -0700 Subject: [PATCH] Add double-click handler Doesn't really do anything yet. The hard part in WPF is figuring out which row and column were clicked. --- CommonWPF/WPFExtensions.cs | 59 ++++++++++++++++++++++++- SourceGenWPF/MainController.cs | 4 ++ SourceGenWPF/ProjWin/MainWindow.xaml | 6 ++- SourceGenWPF/ProjWin/MainWindow.xaml.cs | 32 +++++++++++++- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/CommonWPF/WPFExtensions.cs b/CommonWPF/WPFExtensions.cs index 429453f..a7ff0c7 100644 --- a/CommonWPF/WPFExtensions.cs +++ b/CommonWPF/WPFExtensions.cs @@ -17,6 +17,8 @@ using System; using System.Diagnostics; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; using System.Windows.Media; namespace CommonWPF { @@ -54,8 +56,11 @@ namespace CommonWPF { } /// - /// Add functions to get the element that's currently shown at the top of the ListView - /// window, and to scroll the list so that a specific item is at the top. + /// Helper functions for working with a ListView. + /// + /// ListViews are generalized to an absurd degree, so simple things like "what column did + /// I click on" and "what row is at the top" that were easy in WinForms are not provided + /// by WPF. /// public static class ListViewExtensions { /// @@ -93,5 +98,55 @@ namespace CommonWPF { sv.ScrollToBottom(); lv.ScrollIntoView(item); } + + /// + /// Returns the ListViewItem that was clicked on, or null if an LVI wasn't the target + /// of a click (e.g. off the bottom of the list). + /// + public static ListViewItem GetClickedItem(this ListView lv, MouseButtonEventArgs e) { + DependencyObject dep = (DependencyObject)e.OriginalSource; + + // Should start at something like a TextBlock. Walk up the tree until we hit the + // ListViewItem. + while (dep != null && !(dep is ListViewItem)) { + dep = VisualTreeHelper.GetParent(dep); + } + if (dep == null) { + return null; + } + return (ListViewItem)dep; + } + + /// + /// Determines which column was the target of a mouse click. Only works for ListView + /// with GridView. + /// + /// + /// There's just no other way to do this with ListView. With DataGrid you can do this + /// somewhat reasonably + /// (https://blog.scottlogic.com/2008/12/02/wpf-datagrid-detecting-clicked-cell-and-row.html), + /// but ListView just doesn't want to help. + /// + /// Column index, or -1 if the click was outside the columns (e.g. off the right + /// edge). + public static int GetClickEventColumn(this ListView lv, MouseButtonEventArgs e) { + // There's a bit of padding that seems to offset things. Not sure how to account + // for it, so for now just fudge it. + const int FUDGE = 4; + + Point p = e.GetPosition(lv); + GridView gv = (GridView)lv.View; + double startPos = FUDGE; + for (int index = 0; index < gv.Columns.Count; index++) { + GridViewColumn col = gv.Columns[index]; + + if (p.X < startPos + col.ActualWidth) { + return index; + } + startPos += col.ActualWidth; + } + + return -1; + } } } diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index 90ac14f..9a6067c 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -637,6 +637,10 @@ namespace SourceGenWPF { #region Main window UI event handlers + public void HandleDoubleClick(int row, int col) { + Debug.WriteLine("DCLICK: row=" + row + " col=" + col); + } + public void OpenRecentProject(int projIndex) { if (!CloseProject()) { return; diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml b/SourceGenWPF/ProjWin/MainWindow.xaml index 96f625a..1f2aeb6 100644 --- a/SourceGenWPF/ProjWin/MainWindow.xaml +++ b/SourceGenWPF/ProjWin/MainWindow.xaml @@ -23,7 +23,8 @@ limitations under the License. Title="6502bench SourceGen" Icon="/SourceGenWPF;component/Res/SourceGenIcon.ico" Width="1000" Height="600" MinWidth="800" MinHeight="500" - Loaded="Window_Loaded"> + Loaded="Window_Loaded" + MouseDown="Window_MouseDown"> @@ -305,7 +306,8 @@ limitations under the License. Visibility="{Binding Path=CodeListVisibility}" VirtualizingStackPanel.VirtualizationMode="Recycling" ItemContainerStyle="{StaticResource codeListItemStyle}" - SelectionChanged="CodeListView_SelectionChanged"> + SelectionChanged="CodeListView_SelectionChanged" + MouseDoubleClick="CodeListView_MouseDoubleClick"> diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml.cs b/SourceGenWPF/ProjWin/MainWindow.xaml.cs index 9bf0d33..2bdd068 100644 --- a/SourceGenWPF/ProjWin/MainWindow.xaml.cs +++ b/SourceGenWPF/ProjWin/MainWindow.xaml.cs @@ -75,6 +75,7 @@ namespace SourceGenWPF.ProjWin { CodeDisplayList = new DisplayList(); codeListView.ItemsSource = CodeDisplayList; + // https://dlaa.me/blog/post/9425496 to re-auto-size after data added mMainCtrl = new MainController(this); @@ -211,6 +212,36 @@ namespace SourceGenWPF.ProjWin { get { return mShowCodeListView ? Visibility.Visible : Visibility.Hidden; } } + /// + /// Catch mouse-down events so we can treat the fourth mouse button as "back". + /// + private void Window_MouseDown(object sender, MouseButtonEventArgs e) { + if (e.ChangedButton == MouseButton.XButton1) { + Debug.WriteLine("TODO: navigate back"); + } + } + + /// + /// Handles a double-click on the code list. We have to figure out which row and + /// column were clicked, which is not easy in WPF. + /// + private void CodeListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { + Debug.Assert(sender == codeListView); + + ListViewItem lvi = codeListView.GetClickedItem(e); + if (lvi == null) { + return; + } + DisplayList.FormattedParts parts = (DisplayList.FormattedParts)lvi.Content; + int row = parts.ListIndex; + int col = codeListView.GetClickEventColumn(e); + if (col < 0) { + return; + } + mMainCtrl.HandleDoubleClick(row, col); + } + + #region Selection management private void CodeListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { @@ -526,6 +557,5 @@ namespace SourceGenWPF.ProjWin { new ObservableCollection(); #endregion References panel - } }