diff --git a/src/ui/Features/Assa/AssaAttachmentsWindow.cs b/src/ui/Features/Assa/AssaAttachmentsWindow.cs index 9854238192c..1527e6a0d8e 100644 --- a/src/ui/Features/Assa/AssaAttachmentsWindow.cs +++ b/src/ui/Features/Assa/AssaAttachmentsWindow.cs @@ -92,58 +92,42 @@ private static Border MakeLeftView(AssaAttachmentsViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, }; - var dataGrid = new DataGrid + // No header sorting: the attachment order is written back to the subtitle + // footer ([Fonts]/[Graphics] sections) in list order on OK, so the collection + // order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Attachments; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Attachments, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(AssaAttachmentItem.FileName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Type, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(AssaAttachmentItem.Category)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(AssaAttachmentItem.Size)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedAttachment)) { Source = vm }); + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(AssaAttachmentItem.FileName)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Type, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(AssaAttachmentItem.Category)), + Width = new GridLength(140), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(AssaAttachmentItem.Size)), + Width = new GridLength(100), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedAttachment)) { Source = vm }); dataGrid.SelectionChanged += vm.DataGridSelectionChanged; dataGrid.KeyDown += vm.AttachmentsDataGridKeyDown; - dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => - { - if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) - { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); - e.Handled = true; - } - }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + TableViewExtras.AttachHomeEndNavigation(dataGrid); var flyout = new MenuFlyout(); flyout.Opening += vm.AttachmentsContextMenuOpening; diff --git a/src/ui/Features/Assa/AssaStylePickerWindow.cs b/src/ui/Features/Assa/AssaStylePickerWindow.cs index 3b991613a4b..d0b815473db 100644 --- a/src/ui/Features/Assa/AssaStylePickerWindow.cs +++ b/src/ui/Features/Assa/AssaStylePickerWindow.cs @@ -66,75 +66,76 @@ public AssaStylePickerWindow(AssaStylePickerViewModel vm) private static Border MakeDataGrid(AssaStylePickerViewModel vm) { - var usagesColumn = new DataGridTextColumn + var usagesColumn = new SeTableViewColumn { Header = Se.Language.General.Usages, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, + Width = new GridLength(90), }; - usagesColumn.Bind(DataGridTextColumn.IsVisibleProperty, new Binding(nameof(vm.ShowUsageCount)) + usagesColumn.Bind(SeTableViewColumn.IsVisibleProperty, new Binding(nameof(vm.ShowUsageCount)) { Mode = BindingMode.OneWay, Source = vm, }); - var dataGrid = new DataGrid + // No header sorting: the checked styles are imported/applied in list order + // (e.g. appended to the file's style list, which is written to the header), + // so the collection order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Styles; + + // The usages column has a bound visibility, so all columns go through a + // TableViewColumnManager (TableView itself has no column IsVisible). + var columnManager = new TableViewColumnManager(dataGrid); + columnManager.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Styles, - Columns = + Header = Se.Language.General.Enabled, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - new DataGridTemplateColumn - { - Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border - { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(StyleDisplay.IsSelected)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, - }, - usagesColumn, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedStyle)) { Source = vm }); + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(StyleDisplay.IsSelected)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80), + }); + columnManager.Add(new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); + columnManager.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontName)), + Width = new GridLength(180), + }); + columnManager.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontSize, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontSize)), + Width = new GridLength(90), + }); + columnManager.Add(usagesColumn); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStyle)) { Source = vm }); return UiUtil.MakeBorderForControl(dataGrid); } diff --git a/src/ui/Features/Assa/AssaStylesViewModel.cs b/src/ui/Features/Assa/AssaStylesViewModel.cs index c4528d009ef..1ec4be4d554 100644 --- a/src/ui/Features/Assa/AssaStylesViewModel.cs +++ b/src/ui/Features/Assa/AssaStylesViewModel.cs @@ -1,4 +1,3 @@ -using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media.Imaging; @@ -53,9 +52,16 @@ public partial class AssaStylesViewModel : ObservableObject, IClosingCleanup public Window? Window { get; set; } public bool OkPressed { get; private set; } public string Header { get; set; } - public DataGrid FileStyleGrid { get; set; } - public DataGrid StorageStyleGrid { get; set; } - public DataGridCollectionView StorageStylesView { get; } + public TableView FileStyleGrid { get; set; } + public TableView StorageStyleGrid { get; set; } + + /// + /// The storage styles filtered by the selected category. The DataGrid-era + /// DataGridCollectionView is gone (TableView binds a plain collection), so this + /// view is rebuilt from - which stays the source of + /// truth for saving - whenever the source or the category filter changes. + /// + public ObservableCollection StorageStylesView { get; } public Subtitle ResultSubtitle => _subtitle; private readonly IFileHelper _fileHelper; @@ -81,8 +87,8 @@ public AssaStylesViewModel(IFileHelper fileHelper, IWindowService windowService) BorderTypes = new ObservableCollection(BorderStyleItem.List()); SelectedBorderType = BorderTypes[0]; CurrentTitle = string.Empty; - FileStyleGrid = new DataGrid(); - StorageStyleGrid = new DataGrid(); + FileStyleGrid = new TableView(); + StorageStyleGrid = new TableView(); Header = string.Empty; _subtitle = new Subtitle(); @@ -90,10 +96,9 @@ public AssaStylesViewModel(IFileHelper fileHelper, IWindowService windowService) LoadSettings(); - StorageStylesView = new DataGridCollectionView(StorageStyles) - { - Filter = o => o is StyleDisplay s && IsStyleInSelectedCategory(s), - }; + StorageStylesView = new ObservableCollection(); + StorageStyles.CollectionChanged += (_, _) => RefreshStorageStylesView(); + RefreshStorageStylesView(); RebuildStorageCategories(); _timerUpdatePreview = new System.Timers.Timer(500); @@ -278,7 +283,7 @@ private void FileNew() [RelayCommand] private void FileRemove() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -302,7 +307,7 @@ private void FileRemoveAll() [RelayCommand] private void FilesDuplicate() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -362,7 +367,7 @@ private async Task FileExport() [RelayCommand] private void FileCopyToStorage() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -413,7 +418,7 @@ private async Task FileTakeUsagesFrom() [RelayCommand] private async Task FileReplaceWith() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -538,7 +543,7 @@ private void StorageNew() [RelayCommand] private void StorageRemove() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -594,7 +599,7 @@ private void StorageRemove() } } - StorageStyleGrid.Focus(); + TableViewExtras.FocusRow(StorageStyleGrid); }); } @@ -607,7 +612,7 @@ private void StorageRemoveAll() [RelayCommand] private void StorageDuplicate() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -665,7 +670,7 @@ private async Task StorageExport() [RelayCommand] private void StorageCopyToFiles() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -737,10 +742,24 @@ private void RebuildStorageCategories() partial void OnSelectedStorageCategoryChanged(string value) { - StorageStylesView?.Refresh(); + RefreshStorageStylesView(); IsCategoryActionVisible = value != AllCategoriesLabel && value != DefaultCategoryLabel; } + private void RefreshStorageStylesView() + { + if (StorageStylesView is null) + { + return; // category can change while the constructor is still initializing + } + + StorageStylesView.Clear(); + foreach (var style in StorageStyles.Where(IsStyleInSelectedCategory)) + { + StorageStylesView.Add(style); + } + } + [RelayCommand] private async Task NewCategory() { @@ -852,7 +871,7 @@ private void DeleteCategory() [RelayCommand] private async Task MoveToCategory() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -882,7 +901,7 @@ private async Task MoveToCategory() RebuildStorageCategories(); SelectedStorageCategory = StorageCategories.Contains(label) ? label : SelectedStorageCategory; - StorageStylesView.Refresh(); + RefreshStorageStylesView(); } private void Close() @@ -965,7 +984,7 @@ public void Initialize( } IsFileStyleSelected = SelectedFileStyle != null; - IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems.Count == 1; + IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems?.Count == 1; _timerUpdatePreview.Start(); } @@ -1199,7 +1218,7 @@ private void SwitchToFileStyle() CurrentTitle = Se.Language.Assa.StylesInFile; SelectedBorderType = selectedStyle?.BorderStyle ?? BorderTypes[0]; IsFileStyleSelected = selectedStyle != null; - IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems.Count == 1; + IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems?.Count == 1; } private void SwitchToStorageStyle() @@ -1209,8 +1228,8 @@ private void SwitchToStorageStyle() CurrentTitle = Se.Language.Assa.StylesSaved; SelectedBorderType = selectedStyle?.BorderStyle ?? BorderTypes[0]; IsStorageStyleSelected = selectedStyle != null; - IsSetStyleAsDefaultVisible = StorageStyleGrid.SelectedItems.Count == 1; - IsCopyToFileStylesVisible = StorageStyleGrid.SelectedItems.Count > 0; + IsSetStyleAsDefaultVisible = StorageStyleGrid.SelectedItems?.Count == 1; + IsCopyToFileStylesVisible = StorageStyleGrid.SelectedItems?.Count > 0; } internal void BorderTypeChanged(object? sender, SelectionChangedEventArgs e) @@ -1280,7 +1299,7 @@ private void DeleteFileStyle(StyleDisplay? selectedStyle) UpdateUsages(); } - FileStyleGrid.Focus(); + TableViewExtras.FocusRow(FileStyleGrid); }); } @@ -1330,7 +1349,7 @@ private void DeleteFileStyles(List selectedStyles) } UpdateUsages(); - FileStyleGrid.Focus(); + TableViewExtras.FocusRow(FileStyleGrid); }); } diff --git a/src/ui/Features/Assa/AssaStylesWindow.cs b/src/ui/Features/Assa/AssaStylesWindow.cs index 9bb52c605b1..02f63e83209 100644 --- a/src/ui/Features/Assa/AssaStylesWindow.cs +++ b/src/ui/Features/Assa/AssaStylesWindow.cs @@ -111,67 +111,50 @@ private static Border MakeFileStyles(AssaStylesViewModel vm) var label = UiUtil.MakeLabel(Se.Language.Assa.StylesInFile).WithBold(); - var dataGrid = new DataGrid + // No header sorting: ASSA styles are written to the file header in list + // order on OK, so the collection order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.FileStyles; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.FileStyles, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Usages, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.UsageCount)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedFileStyle)) { Source = vm }); + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontName)), + Width = new GridLength(150), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontSize, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontSize)), + Width = new GridLength(90), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Usages, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.UsageCount)), + Width = new GridLength(90), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFileStyle)) { Source = vm }); dataGrid.SelectionChanged += vm.FileStylesChanged; dataGrid.GotFocus += vm.FileStylesGotFocus; dataGrid.KeyDown += vm.FileStylesKeyDown; - dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => - { - if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) - { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); - e.Handled = true; - } - }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + TableViewExtras.AttachHomeEndNavigation(dataGrid); vm.FileStyleGrid = dataGrid; var flyout = new MenuFlyout(); @@ -277,59 +260,54 @@ private static Border MakeStorageStyles(AssaStylesViewModel vm) var panelCategory = UiUtil.MakeHorizontalPanel(labelCategory, comboBoxCategory, buttonNewCategory, buttonRenameCategory, buttonDeleteCategory) .WithAlignmentLeft(); - var dataGrid = new DataGrid + // No header sorting: the storage style order is persisted to settings in + // list order on OK, so the collection order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.StorageStylesView; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.StorageStylesView, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Category, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Category)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.IsDefault, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.IsDefault)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedStorageStyle)) { Source = vm }); + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Category, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Category)), + Width = new GridLength(120), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontName)), + Width = new GridLength(150), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontSize, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontSize)), + Width = new GridLength(90), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.IsDefault, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.IsDefault)), + Width = new GridLength(90), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStorageStyle)) { Source = vm }); dataGrid.SelectionChanged += vm.StorageStylesChanged; dataGrid.GotFocus += vm.StorageStylesGotFocus; vm.StorageStyleGrid = dataGrid; diff --git a/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs b/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs index 79e09f9c166..d2bc4990b96 100644 --- a/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs +++ b/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs @@ -27,48 +27,55 @@ public FontCollectorWindow(FontCollectorViewModel vm) vm.Window = this; DataContext = vm; - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.FontItems; + + var fontNameColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - CanUserResizeColumns = true, - CanUserSortColumns = true, - IsReadOnly = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.FontItems, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FontCollectorItem.FontName)), - }, - new DataGridTextColumn - { - Header = Se.Language.Assa.FontCollectorUsedIn, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FontCollectorItem.UsedIn)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Status, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FontCollectorItem.Status)), - }, - new DataGridTextColumn - { - Header = Se.Language.Assa.FontCollectorFontFiles, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FontCollectorItem.FileDisplay)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FontCollectorItem.FontName)), + Width = new GridLength(180), + }; + var usedInColumn = new SeTableViewColumn + { + Header = Se.Language.Assa.FontCollectorUsedIn, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FontCollectorItem.UsedIn)), + Width = new GridLength(1, GridUnitType.Star), + }; + var statusColumn = new SeTableViewColumn + { + Header = Se.Language.General.Status, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FontCollectorItem.Status)), + Width = new GridLength(120), }; + var fontFilesColumn = new SeTableViewColumn + { + Header = Se.Language.Assa.FontCollectorFontFiles, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FontCollectorItem.FileDisplay)), + Width = new GridLength(1, GridUnitType.Star), + }; + dataGrid.Columns.Add(fontNameColumn); + dataGrid.Columns.Add(usedInColumn); + dataGrid.Columns.Add(statusColumn); + dataGrid.Columns.Add(fontFilesColumn); + + // Header sorting is safe here: the font list is presentation-only (copying + // fonts to a folder uses the found-file set, not the row order, and the + // background scan holds item references, not indexes). + var sorter = new TableViewHeaderSorter(dataGrid); + sorter.AddSortable(fontNameColumn, x => x.FontName) + .AddSortable(usedInColumn, x => x.UsedIn) + .AddSortable(statusColumn, x => x.Status) + .AddSortable(fontFilesColumn, x => x.FileDisplay); var statusText = new TextBlock { diff --git a/src/ui/Features/Edit/ModifySelection/ModifySelectionWindow.cs b/src/ui/Features/Edit/ModifySelection/ModifySelectionWindow.cs index 5ac3c848d90..9122a8f1343 100644 --- a/src/ui/Features/Edit/ModifySelection/ModifySelectionWindow.cs +++ b/src/ui/Features/Edit/ModifySelection/ModifySelectionWindow.cs @@ -107,42 +107,36 @@ private static Border MakeRulesView(ModifySelectionViewModel vm, out TextBox tex numericUpDownRuleNumber.BindIsVisible(vm, nameof(vm.SelectedRule) + "." + nameof(vm.SelectedRule.HasNumber)); numericUpDownRuleNumber.ValueChanged += (sender, args) => vm.OnRuleChanged(); - var dataGridMultiSelect = new DataGrid + var dataGridMultiSelect = TableViewExtras.MakeTableView(multiSelect: false); + dataGridMultiSelect.CanUserResizeColumns = false; + dataGridMultiSelect.Width = 280; + dataGridMultiSelect.MaxHeight = 200; + dataGridMultiSelect.DataContext = vm; + dataGridMultiSelect.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - MaxHeight = 200, - Columns = + Header = Se.Language.General.Enabled, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - new DataGridTemplateColumn - { - Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border - { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = MakeCheckBox(vm) - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - }, - }; + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = MakeCheckBox(vm) + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80) + }); + dataGridMultiSelect.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); dataGridMultiSelect.BindIsVisible(vm, nameof(vm.SelectedRule) + "." + nameof(vm.SelectedRule.HasMultiSelect)); - dataGridMultiSelect.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.SelectedRule) + "." + nameof(vm.SelectedRule.MultiSelectItems)) { Source = vm }); + dataGridMultiSelect.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.SelectedRule) + "." + nameof(vm.SelectedRule.MultiSelectItems)) { Source = vm }); var panelRule = new StackPanel { @@ -216,71 +210,69 @@ private static Border MakeSubtitleView(ModifySelectionViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // this is a subtitle-line preview in subtitle order, and Ok() iterates the + // collection in order to build the selection. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn - { - Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border + Header = Se.Language.General.Apply, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border + { + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(PreviewItem.Apply)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(PreviewItem.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - Binding = new Binding(nameof(PreviewItem.Show)) { Converter = fullTimeConverter }, - Width = new DataGridLength(120), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - Binding = new Binding(nameof(PreviewItem.Duration)) { Converter = shortTimeConverter }, - Width = new DataGridLength(120), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(PreviewItem.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(PreviewItem.Apply)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80) }, - }; + new SeTableViewColumn + { + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(PreviewItem.Number)), + Width = new GridLength(60), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Show, + Binding = new Binding(nameof(PreviewItem.Show)) { Converter = fullTimeConverter }, + Width = new GridLength(120), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + Binding = new Binding(nameof(PreviewItem.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(120), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(PreviewItem.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); // Bind ItemsSource to the property (rather than assigning the instance once) // so the grid follows the collection when the view model swaps it on preview. - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Subtitles)) { Source = vm }); + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Subtitles)) { Source = vm }); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Edit/MultipleReplace/CategoryExportWindow.cs b/src/ui/Features/Edit/MultipleReplace/CategoryExportWindow.cs index 57e56c17323..02ec251a97d 100644 --- a/src/ui/Features/Edit/MultipleReplace/CategoryExportWindow.cs +++ b/src/ui/Features/Edit/MultipleReplace/CategoryExportWindow.cs @@ -74,47 +74,43 @@ private static Border MakeDataGrid(CategoryExportViewModel vm) Width = double.NaN, }; - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // the caller writes result.Rules to the exported file in collection order, so + // reordering the backing collection would reorder the export. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn + Header = Se.Language.General.Enabled, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!CheckBox.IsCheckedProperty] = new Binding(nameof(RuleTreeNode.IsSelected)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RuleTreeNode.CategoryName)), - IsReadOnly = true, - }, + [!CheckBox.IsCheckedProperty] = new Binding(nameof(RuleTreeNode.IsSelected)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80) }, - }; - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Rules)) { Source = vm }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedRule)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(RuleTreeNode.CategoryName)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Rules)) { Source = vm }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedRule)) { Source = vm }); grid.Add(dataGrid, 0); diff --git a/src/ui/Features/Edit/MultipleReplace/FindRuleWindow.cs b/src/ui/Features/Edit/MultipleReplace/FindRuleWindow.cs index 9a3192b9279..df28e31fe38 100644 --- a/src/ui/Features/Edit/MultipleReplace/FindRuleWindow.cs +++ b/src/ui/Features/Edit/MultipleReplace/FindRuleWindow.cs @@ -66,66 +66,74 @@ public FindRuleWindow(FindRuleViewModel vm) private static Border MakeDataGrid(FindRuleViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + + var columnCategory = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - IsReadOnly = true, - DataContext = vm, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Category, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding("Parent.CategoryName"), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Find, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RuleTreeNode.Find)), - IsReadOnly = true, - Width = new DataGridLength(2, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.ReplaceWith, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RuleTreeNode.ReplaceWith)), - IsReadOnly = true, - Width = new DataGridLength(2, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Description, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RuleTreeNode.Description)), - IsReadOnly = true, - Width = new DataGridLength(2, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Type, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RuleTreeNode.SearchType)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - }, + Header = Se.Language.General.Category, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding("Parent.CategoryName"), + Width = new GridLength(1, GridUnitType.Star), }; + var columnFind = new SeTableViewColumn + { + Header = Se.Language.General.Find, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(RuleTreeNode.Find)), + Width = new GridLength(2, GridUnitType.Star), + }; + var columnReplaceWith = new SeTableViewColumn + { + Header = Se.Language.General.ReplaceWith, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(RuleTreeNode.ReplaceWith)), + Width = new GridLength(2, GridUnitType.Star), + }; + var columnDescription = new SeTableViewColumn + { + Header = Se.Language.General.Description, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(RuleTreeNode.Description)), + Width = new GridLength(2, GridUnitType.Star), + }; + var columnType = new SeTableViewColumn + { + Header = Se.Language.General.Type, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(RuleTreeNode.SearchType)), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(140), + }; + dataGrid.Columns.AddRange(new TableViewColumn[] + { + columnCategory, columnFind, columnReplaceWith, columnDescription, columnType, + }); + + // Search results whose order is presentation-only (the caller consumes just + // SelectedRule), so header sorting is safe to wire. + var sorter = new TableViewHeaderSorter(dataGrid); + sorter.AddSortable(columnCategory, x => x.Parent?.CategoryName ?? string.Empty) + .AddSortable(columnFind, x => x.Find) + .AddSortable(columnReplaceWith, x => x.ReplaceWith) + .AddSortable(columnDescription, x => x.Description) + .AddSortable(columnType, x => x.SearchType); - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Rules)) { Source = vm }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedRule)) { Source = vm, Mode = BindingMode.TwoWay }); - dataGrid.DoubleTapped += vm.DataGridDoubleTapped; + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Rules)) { Source = vm }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedRule)) { Source = vm, Mode = BindingMode.TwoWay }); + dataGrid.DoubleTapped += (sender, e) => + { + // A fast double-click on a sortable header must sort, not accept the dialog. + if (!TableViewExtras.IsInColumnHeader(e.Source as Avalonia.Visual)) + { + vm.DataGridDoubleTapped(sender, e); + } + }; return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs index ee43224b341..b1f2ae5d6c8 100644 --- a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs +++ b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs @@ -390,81 +390,77 @@ private static Border MakeFixesView(MultipleReplaceViewModel vm) editGrid.Add(labelType, 0, 2); editGrid.Add(comboBoxType, 1, 2); - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // this is a fix preview in subtitle order, and the replace rules themselves run + // in list order - reordering the backing collection would scramble the preview. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Fixes; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Fixes, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn - { - Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border - { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(MultipleReplaceFix.Apply)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MultipleReplaceFix.Number)), - IsReadOnly = true, - }, - new DataGridTemplateColumn - { - Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => + Header = Se.Language.General.Apply, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - var (beforeBlock, _) = TextDiffHighlighter.CompareReplacement(item.Before, item.After); - return new Border + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, - Padding = new Thickness(4), - Child = beforeBlock, - }; + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(MultipleReplaceFix.Apply)), + HorizontalAlignment = HorizontalAlignment.Center + } }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTemplateColumn + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) + }, + new SeTableViewColumn + { + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MultipleReplaceFix.Number)), + Width = new GridLength(60), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Before, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => { - Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => + var (beforeBlock, _) = TextDiffHighlighter.CompareReplacement(item.Before, item.After); + return new Border { - var (_, afterBlock) = TextDiffHighlighter.CompareReplacement(item.Before, item.After); - return new Border - { - Background = Brushes.Transparent, - Padding = new Thickness(4), - Child = afterBlock, - }; - }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Background = Brushes.Transparent, + Padding = new Thickness(4), + Child = beforeBlock, + }; + }), + Width = new GridLength(1, GridUnitType.Star), }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedFix)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.After, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + { + var (_, afterBlock) = TextDiffHighlighter.CompareReplacement(item.Before, item.After); + return new Border + { + Background = Brushes.Transparent, + Padding = new Thickness(4), + Child = afterBlock, + }; + }), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFix)) { Source = vm }); var hitsItemsControl = new ItemsControl { diff --git a/src/ui/Features/Files/ImportImages/ImportImagesWindow.cs b/src/ui/Features/Files/ImportImages/ImportImagesWindow.cs index 45e7c96fd8c..22b51e7acf9 100644 --- a/src/ui/Features/Files/ImportImages/ImportImagesWindow.cs +++ b/src/ui/Features/Files/ImportImages/ImportImagesWindow.cs @@ -91,62 +91,56 @@ private static Border MakeImagesView(ImportImagesViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); var fileSizeConverter = new FileSizeConverter(); - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // the caller feeds result.Images to OCR in collection order, so reordering the + // backing collection would reorder the resulting subtitle. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Images; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Images, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ImportImageItem.FileName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ImportImageItem.Size)) { Converter = fileSizeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ImportImageItem.Start)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Hide, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ImportImageItem.End)){ Converter = fullTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ImportImageItem.Duration)){ Converter = shortTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ImportImageItem.FileName)), + Width = new GridLength(1, GridUnitType.Star), }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedImage)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ImportImageItem.Size)) { Converter = fileSizeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(100), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ImportImageItem.Start)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(130), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Hide, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ImportImageItem.End)){ Converter = fullTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(130), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ImportImageItem.Duration)){ Converter = shortTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(110), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedImage)) { Source = vm }); dataGrid.SelectionChanged += vm.DataGridSelectionChanged; dataGrid.KeyDown += vm.AttachmentsDataGridKeyDown; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => @@ -155,12 +149,16 @@ private static Border MakeImagesView(ImportImagesViewModel vm) { var target = e.Key == Key.Home ? items[0] : items[^1]; dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + if (target != null) + { + dataGrid.ScrollIntoView(target); + } + e.Handled = true; } }, Avalonia.Interactivity.RoutingStrategies.Tunnel); - // hack to make drag and drop work on the DataGrid - also on empty rows + // hack to make drag and drop work on the grid - also on empty rows var dropHost = new Border { Background = Brushes.Transparent, diff --git a/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs b/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs index e403328bdc4..aab451ca8db 100644 --- a/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs +++ b/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs @@ -156,41 +156,36 @@ private static Grid MakeTextBoxAndControlsView(ImportPlainTextViewModel vm) textBox.TextChanged += (s, e) => vm.PlainTextChanged(); var sizeConverter = new FileSizeConverter(); - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // the preview builds one subtitle line per file in collection order, so + // reordering the backing collection would reorder the imported lines. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Height = 348; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Files; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = 348, - DataContext = vm, - ItemsSource = vm.Files, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(DisplayFile.FileName)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(DisplayFile.Size)) { Converter = sizeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(DisplayFile.FileName)), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(1, GridUnitType.Star), }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedFile)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(DisplayFile.Size)) { Converter = sizeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(100), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFile)) { Source = vm }); - // hack to make drag and drop work on the DataGrid - also on empty rows + // hack to make drag and drop work on the grid - also on empty rows var dropHost = new Border { Background = Brushes.Transparent, @@ -299,64 +294,57 @@ private static Border MakeSubtitleGridView(ImportPlainTextViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var fileSizeConverter = new FileSizeConverter(); - var dataGrid = new DataGrid + + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // this is the subtitle preview, consumed by the caller in collection order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Subtitles; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Hide, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(130), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Hide, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(130), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(110), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle)) { Source = vm }); grid.Add(dataGrid, 0); diff --git a/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingWindow.cs b/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingWindow.cs index 7d340ef5247..a8576b06075 100644 --- a/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingWindow.cs +++ b/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingWindow.cs @@ -80,45 +80,57 @@ public ManualChosenEncodingWindow(ManualChosenEncodingViewModel vm) private static Border MakeEncodingsView(ManualChosenEncodingViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Encodings; + + var columnCodePage = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Encodings, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.File.ManualChosenEncoding.CodePage, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding("Encoding.CodePage"), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TextEncoding.DisplayName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Group, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding("Encoding.BodyName"), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.File.ManualChosenEncoding.CodePage, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding("Encoding.CodePage"), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(100), }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedEncoding)) { Source = vm }); + var columnName = new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TextEncoding.DisplayName)), + Width = new GridLength(1, GridUnitType.Star), + }; + var columnGroup = new SeTableViewColumn + { + Header = Se.Language.General.Group, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding("Encoding.BodyName"), + Width = new GridLength(180), + }; + dataGrid.Columns.AddRange(new TableViewColumn[] { columnCodePage, columnName, columnGroup }); + + // An encoding pick list whose order is presentation-only (the caller consumes + // just SelectedEncoding), so header sorting is safe to wire. + static string GetBodyName(TextEncoding encoding) + { + try + { + return encoding.Encoding.BodyName; + } + catch + { + return string.Empty; // some code pages have no body name + } + } + + var sorter = new TableViewHeaderSorter(dataGrid); + sorter.AddSortable(columnCodePage, x => x.Encoding.CodePage) + .AddSortable(columnName, x => x.DisplayName) + .AddSortable(columnGroup, GetBodyName); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedEncoding)) { Source = vm }); dataGrid.SelectionChanged += vm.EncodingChanged; return UiUtil.MakeBorderForControlNoPadding(dataGrid); diff --git a/src/ui/Features/Options/Settings/ProfilesExportWindow.cs b/src/ui/Features/Options/Settings/ProfilesExportWindow.cs index 680d012a095..061c1d87dd7 100644 --- a/src/ui/Features/Options/Settings/ProfilesExportWindow.cs +++ b/src/ui/Features/Options/Settings/ProfilesExportWindow.cs @@ -81,47 +81,43 @@ private static Border MakeDataGrid(ProfilesExportViewModel vm) Width = double.NaN, }; - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // the caller writes result.Profiles to the exported .profile file in collection + // order, so reordering the backing collection would reorder the export. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn + Header = Se.Language.General.Enabled, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!CheckBox.IsCheckedProperty] = new Binding(nameof(ProfileDisplay.IsSelected)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.Name)), - IsReadOnly = true, - }, + [!CheckBox.IsCheckedProperty] = new Binding(nameof(ProfileDisplay.IsSelected)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80) }, - }; - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); grid.Add(dataGrid, 0); diff --git a/src/ui/Features/Options/Settings/ProfilesWindow.cs b/src/ui/Features/Options/Settings/ProfilesWindow.cs index eebdb589d5f..b11a21dccd3 100644 --- a/src/ui/Features/Options/Settings/ProfilesWindow.cs +++ b/src/ui/Features/Options/Settings/ProfilesWindow.cs @@ -81,45 +81,41 @@ private static Border MakeDataGrid(ProfilesViewModel vm) Width = double.NaN, }; - var dataGrid = new DataGrid + // No header sorting (the DataGrid's CanUserSortColumns is not carried over): + // the caller takes result.Profiles in collection order and saves it back to the + // settings, so reordering the backing collection would reorder the saved profiles. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.Options.Settings.SingleLineMaxLength, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.SingleLineMaxLength)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.MaxCharactersPerSecond, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.MaxCharsPerSec)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.Name)), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(1, GridUnitType.Star), }, - }; - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); + new SeTableViewColumn + { + Header = Se.Language.Options.Settings.SingleLineMaxLength, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.SingleLineMaxLength)), + Width = new GridLength(180), + }, + new SeTableViewColumn + { + Header = Se.Language.General.MaxCharactersPerSecond, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.MaxCharsPerSec)), + Width = new GridLength(200), + }, + }); + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); var buttonNew = UiUtil.MakeButton(vm.NewCommand, IconNames.New, Se.Language.General.NewProfile); var buttonExport = UiUtil.MakeButton(vm.ExportCommand, IconNames.Export, Se.Language.General.ExportDotDotDot); diff --git a/src/ui/Features/Shared/PickFontName/PickFontNameViewModel.cs b/src/ui/Features/Shared/PickFontName/PickFontNameViewModel.cs index c0432acd342..76cf8141251 100644 --- a/src/ui/Features/Shared/PickFontName/PickFontNameViewModel.cs +++ b/src/ui/Features/Shared/PickFontName/PickFontNameViewModel.cs @@ -194,7 +194,7 @@ internal void OnKeyDown(KeyEventArgs e) } } - internal void DataGridFontNameSelectionChanged(object? sender, SelectionChangedEventArgs e) + internal void FontNameGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { _dirtyPreview = true; } diff --git a/src/ui/Features/Shared/PickFontName/PickFontNameWindow.cs b/src/ui/Features/Shared/PickFontName/PickFontNameWindow.cs index abb4e962a9c..9f3d54065ec 100644 --- a/src/ui/Features/Shared/PickFontName/PickFontNameWindow.cs +++ b/src/ui/Features/Shared/PickFontName/PickFontNameWindow.cs @@ -114,32 +114,29 @@ public PickFontNameWindow(PickFontNameViewModel vm) private static Border MakeFontsView(PickFontNameViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.FontNames; + + var fontNameColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.FontNames, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding("."), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding("."), + Width = new GridLength(1, GridUnitType.Star), }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedFontName))); - dataGrid.SelectionChanged += vm.DataGridFontNameSelectionChanged; + dataGrid.Columns.Add(fontNameColumn); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFontName))); + dataGrid.SelectionChanged += vm.FontNameGridSelectionChanged; + + // Font list order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. Note a new search resets the order. + new TableViewHeaderSorter(dataGrid) + .AddSortable(fontNameColumn, x => x); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterViewModel.cs b/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterViewModel.cs index a339ffb4f78..22fb3c530eb 100644 --- a/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterViewModel.cs +++ b/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterViewModel.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System; using System.Collections.Generic; @@ -24,13 +25,13 @@ public partial class PickLayerFilterViewModel : ObservableObject public bool OkPressed { get; private set; } public List? SelectedLayers { get; private set; } - public DataGrid LayerGrid { get; set; } + public TableView LayerGrid { get; set; } public PickLayerFilterViewModel() { Layers = new ObservableCollection(); SelectedLayers = new List(); - LayerGrid = new DataGrid(); + LayerGrid = new TableView(); HideFromWaveform = Se.Settings.Assa.HideLayersFromWaveform; HideFromGridView = Se.Settings.Assa.HideLayersFromSubtitleGrid; HideFromVideoPreview = Se.Settings.Assa.HideLayersFromVideoPreview; @@ -130,38 +131,33 @@ internal void Loaded() SelectAndScrollToRow(LayerGrid, 0); } - private void SelectAndScrollToRow(DataGrid? datagrid, int index) + private void SelectAndScrollToRow(TableView? tableView, int index) { - if (index < 0 || datagrid == null) + if (index < 0 || tableView == null) { return; } Dispatcher.UIThread.Post(() => { - datagrid.Focus(); + if (tableView.SelectedIndex != index) + { + tableView.SelectedIndex = index; + } - if (datagrid.SelectedIndex != index) + if (tableView.SelectedItem is { } selectedItem) { - datagrid.SelectedIndex = index; + tableView.ScrollIntoView(selectedItem); } - datagrid.ScrollIntoView(datagrid.SelectedItem, null); + TableViewExtras.FocusRow(tableView); }); } + // Space (toggle checkbox) is handled by TableViewExtras.AddSpaceToggle in the window. internal void LayerGridKeyDown(KeyEventArgs e) { - if (e.Key == Key.Space) - { - e.Handled = true; - var selectedItem = LayerGrid.SelectedItem as LayerItem; - if (selectedItem != null) - { - selectedItem.IsSelected = !selectedItem.IsSelected; - } - } - else if (e.Key == Key.Enter || e.Key == Key.Return) + if (e.Key == Key.Enter || e.Key == Key.Return) { e.Handled = true; Ok(); diff --git a/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterWindow.cs b/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterWindow.cs index bb4de804822..c9614d4b992 100644 --- a/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterWindow.cs +++ b/src/ui/Features/Shared/PickLayerFilter/PickLayerFilterWindow.cs @@ -97,67 +97,84 @@ private static Border MakeSettings(PickLayerFilterViewModel vm) private static Border MakeLayersView(PickLayerFilterViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Layers; + + var visibleColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Layers, - Columns = + Header = Se.Language.General.Visible, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border { - new DataGridTemplateColumn - { - Header = Se.Language.General.Visible, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border - { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(LayerItem.IsSelected)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Layer, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(LayerItem.Layer)), - IsReadOnly = true, - }, - new DataGridTextColumn + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Header = Se.Language.General.Usages, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(LayerItem.UsageCount)), - IsReadOnly = true, - }, - }, + Focusable = false, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(LayerItem.IsSelected)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80), + }; + dataGrid.Columns.Add(visibleColumn); + + var layerColumn = new SeTableViewColumn + { + Header = Se.Language.General.Layer, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(LayerItem.Layer)), + Width = new GridLength(100), }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedLayer))); + dataGrid.Columns.Add(layerColumn); + + var usagesColumn = new SeTableViewColumn + { + Header = Se.Language.General.Usages, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(LayerItem.UsageCount)), + Width = new GridLength(1, GridUnitType.Star), + }; + dataGrid.Columns.Add(usagesColumn); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedLayer))); vm.LayerGrid = dataGrid; dataGrid.KeyDown += (s, e) => vm.LayerGridKeyDown(e); + + // Space toggles the checkbox (tunnel handler, so it runs before the ListBox + // machinery can swallow the key). + TableViewExtras.AddSpaceToggle(dataGrid, + item => item.IsSelected, (item, v) => item.IsSelected = v); + dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { var target = e.Key == Key.Home ? items[0] : items[^1]; dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + if (target != null) + { + dataGrid.ScrollIntoView(target); + } + e.Handled = true; } }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + // Layer list order is presentation-only: OK collects the checked layers as a + // set of layer numbers, so in-place sorting is safe. + new TableViewHeaderSorter(dataGrid) + .AddSortable(layerColumn, x => x.Layer) + .AddSortable(usagesColumn, x => x.UsageCount); + return UiUtil.MakeBorderForControlNoPadding(dataGrid); } } diff --git a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs index f5d537e6f08..d81038b9872 100644 --- a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs +++ b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs @@ -33,7 +33,7 @@ public partial class PickMatroskaTrackViewModel : ObservableObject [ObservableProperty] private string _subtitleCountText; public Window? Window { get; set; } - public DataGrid TracksGrid { get; set; } + public TableView TracksGrid { get; set; } public MatroskaTrackInfo? SelectedMatroskaTrack { get; set; } public bool OkPressed { get; private set; } public string WindowTitle { get; private set; } @@ -62,7 +62,7 @@ public PickMatroskaTrackViewModel(IFileHelper fileHelper, IWindowService windowS _fileHelper = fileHelper; _windowService = windowService; Tracks = new ObservableCollection(); - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); WindowTitle = string.Empty; SubtitleCountText = string.Empty; Rows = new ObservableCollection(); @@ -217,14 +217,14 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs e) Cancel(); e.Handled = true; } - else if (e.Key == Key.Enter && TracksGrid.IsFocused) + else if (e.Key == Key.Enter && TracksGrid.IsKeyboardFocusWithin) { Ok(); e.Handled = true; } } - internal void DataGridTracksSelectionChanged(object? sender, SelectionChangedEventArgs e) + internal void TracksGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { _ = TrackChangedAsync(); } @@ -415,7 +415,11 @@ internal void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { TracksGrid.SelectedIndex = index; - TracksGrid.ScrollIntoView(TracksGrid.SelectedItem, null); + if (TracksGrid.SelectedItem is { } selectedItem) + { + TracksGrid.ScrollIntoView(selectedItem); + } + _ = TrackChangedAsync(); }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackWindow.cs b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackWindow.cs index 10f76dacfc9..5895fc07d93 100644 --- a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackWindow.cs +++ b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackWindow.cs @@ -70,7 +70,7 @@ public PickMatroskaTrackWindow(PickMatroskaTrackViewModel vm) Dispatcher.UIThread.InvokeAsync(() => { vm.SelectAndScrollToRow(0); - vm.TracksGrid.Focus(); + TableViewExtras.FocusRow(vm.TracksGrid); }, DispatcherPriority.Input); }; } @@ -78,70 +78,86 @@ public PickMatroskaTrackWindow(PickMatroskaTrackViewModel vm) private static Border MakeTracksView(PickMatroskaTrackViewModel vm) { var booleanToCheckMarkConverter = new BooleanToCheckMarkConverter(); - var dataGridTracks = new DataGrid + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = vm; + dataGridTracks.ItemsSource = vm.Tracks; + + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star, so the + // narrow columns get fixed widths and Name becomes the star column (the old + // grid's star was on the trailing Forced column, which only fills space). + var numberColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.TrackNumber)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name , - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Language)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Codec, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Codec)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Default, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.IsDefault)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Forced, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaTrackInfoDisplay.IsForced)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter}, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.TrackNumber)), + Width = new GridLength(60), }; - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack))); - dataGridTracks.SelectionChanged += vm.DataGridTracksSelectionChanged; + var nameColumn = new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }; + var languageColumn = new SeTableViewColumn + { + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Language)), + Width = new GridLength(100), + }; + var codecColumn = new SeTableViewColumn + { + Header = Se.Language.General.Codec, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.Codec)), + Width = new GridLength(140), + }; + var defaultColumn = new SeTableViewColumn + { + Header = Se.Language.General.Default, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.IsDefault)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter }, + Width = new GridLength(80), + }; + var forcedColumn = new SeTableViewColumn + { + Header = Se.Language.General.Forced, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaTrackInfoDisplay.IsForced)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter }, + Width = new GridLength(80), + }; + + dataGridTracks.Columns.Add(numberColumn); + dataGridTracks.Columns.Add(nameColumn); + dataGridTracks.Columns.Add(languageColumn); + dataGridTracks.Columns.Add(codecColumn); + dataGridTracks.Columns.Add(defaultColumn); + dataGridTracks.Columns.Add(forcedColumn); + + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack))); + dataGridTracks.SelectionChanged += vm.TracksGridSelectionChanged; dataGridTracks.DoubleTapped += (_, _) => vm.OkCommand.Execute(null); vm.TracksGrid = dataGridTracks; + // Track order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. + new TableViewHeaderSorter(dataGridTracks) + .AddSortable(numberColumn, x => x.TrackNumber) + .AddSortable(nameColumn, x => x.Name) + .AddSortable(languageColumn, x => x.Language) + .AddSortable(codecColumn, x => x.Codec) + .AddSortable(defaultColumn, x => x.IsDefault) + .AddSortable(forcedColumn, x => x.IsForced); + return UiUtil.MakeBorderForControlNoPadding(dataGridTracks); } @@ -149,46 +165,45 @@ private static Border MakeSubtitleView(PickMatroskaTrackViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Rows; + + // No sorter here: the preview shows subtitle cues in subtitle order. + dataGridSubtitle.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Rows, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Number)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, + Width = new GridLength(90), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.TextOrImage, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new FuncDataTemplate((item, _) => { var stackPanel = new StackPanel @@ -225,8 +240,7 @@ private static Border MakeSubtitleView(PickMatroskaTrackViewModel vm) return stackPanel; }) }, - }, - }; + }); return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); } diff --git a/src/ui/Features/Shared/PickMp4Track/PickMp4TrackViewModel.cs b/src/ui/Features/Shared/PickMp4Track/PickMp4TrackViewModel.cs index 44b669cc8da..97865b19805 100644 --- a/src/ui/Features/Shared/PickMp4Track/PickMp4TrackViewModel.cs +++ b/src/ui/Features/Shared/PickMp4Track/PickMp4TrackViewModel.cs @@ -29,7 +29,7 @@ public partial class PickMp4TrackViewModel : ObservableObject [ObservableProperty] private string _subtitleCountText; public Window? Window { get; set; } - public DataGrid TracksGrid { get; set; } + public TableView TracksGrid { get; set; } public Mp4TrackInfoDisplay? SelectedMatroskaTrack { get; set; } public bool OkPressed { get; private set; } public string WindowTitle { get; private set; } @@ -45,7 +45,7 @@ public PickMp4TrackViewModel(IFileHelper fileHelper, IWindowService windowServic _fileHelper = fileHelper; _windowService = windowService; Tracks = new ObservableCollection(); - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); WindowTitle = string.Empty; SubtitleCountText = string.Empty; Rows = new ObservableCollection(); @@ -192,7 +192,7 @@ internal void OnKeyDown(KeyEventArgs e) } } - internal void DataGridTracksSelectionChanged(object? sender, SelectionChangedEventArgs e) + internal void TracksGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { bool flowControl = TrackChanged(); if (!flowControl) @@ -251,7 +251,11 @@ internal void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { TracksGrid.SelectedIndex = index; - TracksGrid.ScrollIntoView(TracksGrid.SelectedItem, null); + if (TracksGrid.SelectedItem is { } selectedItem) + { + TracksGrid.ScrollIntoView(selectedItem); + } + TrackChanged(); }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Shared/PickMp4Track/PickMp4TrackWindow.cs b/src/ui/Features/Shared/PickMp4Track/PickMp4TrackWindow.cs index afc21125be4..d1db293c992 100644 --- a/src/ui/Features/Shared/PickMp4Track/PickMp4TrackWindow.cs +++ b/src/ui/Features/Shared/PickMp4Track/PickMp4TrackWindow.cs @@ -80,60 +80,73 @@ protected override void OnLoaded(RoutedEventArgs e) private Border MakeTracksView(PickMp4TrackViewModel vm) { - var dataGridTracks = new DataGrid + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = _vm; + dataGridTracks.ItemsSource = _vm.Tracks; + + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star, so the + // narrow columns get fixed widths and Name becomes the star column. + var handlerColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = _vm, - ItemsSource = _vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = "HandlerName", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(Mp4TrackInfoDisplay.HandlerType)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = "Name", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(Mp4TrackInfoDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = "Duration", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(Mp4TrackInfoDisplay.Duration)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = "IsVobSubSubtitle", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(Mp4TrackInfoDisplay.IsVobSubSubtitle)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = "StartPosition", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(Mp4TrackInfoDisplay.StartPosition)), - IsReadOnly = true, - }, - }, + Header = "HandlerName", + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(Mp4TrackInfoDisplay.HandlerType)), + Width = new GridLength(120), }; - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(_vm.SelectedTrack))); - dataGridTracks.SelectionChanged += vm.DataGridTracksSelectionChanged; - vm.TracksGrid = dataGridTracks; + var nameColumn = new SeTableViewColumn + { + Header = "Name", + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(Mp4TrackInfoDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }; + var durationColumn = new SeTableViewColumn + { + Header = "Duration", + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(Mp4TrackInfoDisplay.Duration)), + Width = new GridLength(100), + }; + var vobSubColumn = new SeTableViewColumn + { + Header = "IsVobSubSubtitle", + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(Mp4TrackInfoDisplay.IsVobSubSubtitle)), + Width = new GridLength(130), + }; + var startPositionColumn = new SeTableViewColumn + { + Header = "StartPosition", + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(Mp4TrackInfoDisplay.StartPosition)), + Width = new GridLength(110), + }; + + dataGridTracks.Columns.Add(handlerColumn); + dataGridTracks.Columns.Add(nameColumn); + dataGridTracks.Columns.Add(durationColumn); + dataGridTracks.Columns.Add(vobSubColumn); + dataGridTracks.Columns.Add(startPositionColumn); + + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(_vm.SelectedTrack))); + dataGridTracks.SelectionChanged += vm.TracksGridSelectionChanged; + vm.TracksGrid = dataGridTracks; + + // Track order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. + new TableViewHeaderSorter(dataGridTracks) + .AddSortable(handlerColumn, x => x.HandlerType) + .AddSortable(nameColumn, x => x.Name) + .AddSortable(durationColumn, x => x.Duration) + .AddSortable(vobSubColumn, x => x.IsVobSubSubtitle) + .AddSortable(startPositionColumn, x => x.StartPosition); return UiUtil.MakeBorderForControlNoPadding(dataGridTracks); } @@ -142,45 +155,45 @@ private Border MakeSubtitleView(PickMp4TrackViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = _vm; + dataGridSubtitle.ItemsSource = _vm.Rows; + + // No sorter here: the preview shows subtitle cues in subtitle order. + dataGridSubtitle.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = _vm, - ItemsSource = _vm.Rows, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = "#", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(Mp4SubtitleCueDisplay.Number)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = "Show", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(Mp4SubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = "Duration", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(Mp4SubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, + Width = new GridLength(90), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = "Text/Image", - IsReadOnly = true, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new FuncDataTemplate((item, _) => { var stackPanel = new StackPanel @@ -217,8 +230,7 @@ private Border MakeSubtitleView(PickMp4TrackViewModel vm) return stackPanel; }) }, - }, - }; + }); return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); } diff --git a/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileViewModel.cs b/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileViewModel.cs index 6d675c4a82f..8d977cf47c3 100644 --- a/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileViewModel.cs +++ b/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileViewModel.cs @@ -71,7 +71,7 @@ internal void KeyDown(object? sender, KeyEventArgs e) } } - internal void DataGridDown(object? sender, KeyEventArgs e) + internal void ProfileGridKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Enter) { @@ -80,7 +80,7 @@ internal void DataGridDown(object? sender, KeyEventArgs e) } } - public void DataGridDoubleTapped(object? sender, TappedEventArgs e) + public void ProfileGridDoubleTapped(object? sender, TappedEventArgs e) { if (SelectedProfile != null) { diff --git a/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileWindow.cs b/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileWindow.cs index 2e6e34bd1ee..37c2427a88f 100644 --- a/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileWindow.cs +++ b/src/ui/Features/Shared/PickRuleProfile/PickRuleProfileWindow.cs @@ -11,7 +11,7 @@ namespace Nikse.SubtitleEdit.Features.Shared.PickRuleProfile; public class PickRuleProfileWindow : Window { - private static DataGrid? _profileDataGrid; + private static TableView? _profileGrid; public PickRuleProfileWindow(PickRuleProfileViewModel vm) { @@ -54,60 +54,72 @@ public PickRuleProfileWindow(PickRuleProfileViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(MakeDataGrid(vm), 0); + grid.Add(MakeProfilesView(vm), 0); grid.Add(panelButtons, 1); Content = grid; - Activated += delegate { _profileDataGrid?.Focus(); }; // hack to make OnKeyDown work + Activated += delegate + { + if (_profileGrid != null) + { + TableViewExtras.FocusRow(_profileGrid); // hack to make OnKeyDown work + } + }; KeyDown += vm.KeyDown; } - private static Border MakeDataGrid(PickRuleProfileViewModel vm) + private static Border MakeProfilesView(PickRuleProfileViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star, so the + // number columns get fixed widths and Name becomes the star column (the old + // grid's star was on the trailing max-CPS column, which only fills space). + var nameColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - IsReadOnly = true, - DataContext = vm, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.Options.Settings.SingleLineMaxLength, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.SingleLineMaxLength)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.MaxCharactersPerSecond, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ProfileDisplay.MaxCharsPerSec)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), }; - dataGrid.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); - dataGrid.AddHandler(KeyDownEvent, vm.DataGridDown, RoutingStrategies.Tunnel); - dataGrid.DoubleTapped += vm.DataGridDoubleTapped; - _profileDataGrid = dataGrid; + var singleLineMaxLengthColumn = new SeTableViewColumn + { + Header = Se.Language.Options.Settings.SingleLineMaxLength, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.SingleLineMaxLength)), + Width = new GridLength(180), + }; + var maxCpsColumn = new SeTableViewColumn + { + Header = Se.Language.General.MaxCharactersPerSecond, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ProfileDisplay.MaxCharsPerSec)), + Width = new GridLength(180), + }; + + dataGrid.Columns.Add(nameColumn); + dataGrid.Columns.Add(singleLineMaxLengthColumn); + dataGrid.Columns.Add(maxCpsColumn); + + dataGrid.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Profiles)) { Source = vm }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedProfile)) { Source = vm }); + dataGrid.AddHandler(KeyDownEvent, vm.ProfileGridKeyDown, RoutingStrategies.Tunnel); + dataGrid.DoubleTapped += vm.ProfileGridDoubleTapped; + _profileGrid = dataGrid; + + // Profile list order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. + new TableViewHeaderSorter(dataGrid) + .AddSortable(nameColumn, x => x.Name) + .AddSortable(singleLineMaxLengthColumn, x => x.SingleLineMaxLength) + .AddSortable(maxCpsColumn, x => x.MaxCharsPerSec); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Shared/PickTsTrack/PickTsTrackViewModel.cs b/src/ui/Features/Shared/PickTsTrack/PickTsTrackViewModel.cs index dee43fd6121..355a70f1053 100644 --- a/src/ui/Features/Shared/PickTsTrack/PickTsTrackViewModel.cs +++ b/src/ui/Features/Shared/PickTsTrack/PickTsTrackViewModel.cs @@ -22,7 +22,7 @@ public partial class PickTsTrackViewModel : ObservableObject [ObservableProperty] private string _subtitleCountText; public Window? Window { get; set; } - public DataGrid TracksGrid { get; set; } + public TableView TracksGrid { get; set; } public bool OkPressed { get; private set; } public string WindowTitle { get; private set; } public Subtitle TeletextSubtitle { get; private set; } @@ -33,7 +33,7 @@ public partial class PickTsTrackViewModel : ObservableObject public PickTsTrackViewModel() { Tracks = new ObservableCollection(); - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); WindowTitle = string.Empty; SubtitleCountText = string.Empty; Rows = new ObservableCollection(); @@ -119,7 +119,7 @@ internal void OnKeyDown(KeyEventArgs e) } } - internal void DataGridTracksSelectionChanged(object? sender, SelectionChangedEventArgs e) + internal void TracksGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { bool flowControl = TrackChanged(); if (!flowControl) @@ -190,7 +190,11 @@ internal void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { TracksGrid.SelectedIndex = index; - TracksGrid.ScrollIntoView(TracksGrid.SelectedItem, null); + if (TracksGrid.SelectedItem is { } selectedItem) + { + TracksGrid.ScrollIntoView(selectedItem); + } + TrackChanged(); }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Shared/PickTsTrack/PickTsTrackWindow.cs b/src/ui/Features/Shared/PickTsTrack/PickTsTrackWindow.cs index 407928135ff..84da95ae51e 100644 --- a/src/ui/Features/Shared/PickTsTrack/PickTsTrackWindow.cs +++ b/src/ui/Features/Shared/PickTsTrack/PickTsTrackWindow.cs @@ -71,69 +71,85 @@ public PickTsTrackWindow(PickTsTrackViewModel vm) private Border MakeTracksView(PickTsTrackViewModel vm) { - var dataGridTracks = new DataGrid + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = vm; + dataGridTracks.ItemsSource = vm.Tracks; + + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star, so the + // narrow columns get fixed widths and Name becomes the star column. + var numberColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.TrackNumber)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name , - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.Language)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Codec, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.Codec)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Default, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.IsDefault)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Forced, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(TsTrackInfoDisplay.IsForced)), - IsReadOnly = true, - }, - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.TrackNumber)), + Width = new GridLength(60), }; - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack))); - dataGridTracks.SelectionChanged += vm.DataGridTracksSelectionChanged; + var nameColumn = new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }; + var languageColumn = new SeTableViewColumn + { + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.Language)), + Width = new GridLength(100), + }; + var codecColumn = new SeTableViewColumn + { + Header = Se.Language.General.Codec, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.Codec)), + Width = new GridLength(140), + }; + var defaultColumn = new SeTableViewColumn + { + Header = Se.Language.General.Default, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.IsDefault)), + Width = new GridLength(80), + }; + var forcedColumn = new SeTableViewColumn + { + Header = Se.Language.General.Forced, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(TsTrackInfoDisplay.IsForced)), + Width = new GridLength(80), + }; + + dataGridTracks.Columns.Add(numberColumn); + dataGridTracks.Columns.Add(nameColumn); + dataGridTracks.Columns.Add(languageColumn); + dataGridTracks.Columns.Add(codecColumn); + dataGridTracks.Columns.Add(defaultColumn); + dataGridTracks.Columns.Add(forcedColumn); + + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack))); + dataGridTracks.SelectionChanged += vm.TracksGridSelectionChanged; dataGridTracks.DoubleTapped += (s, e) => vm.OkCommand.Execute(null); vm.TracksGrid = dataGridTracks; + // Track order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. + new TableViewHeaderSorter(dataGridTracks) + .AddSortable(numberColumn, x => x.TrackNumber) + .AddSortable(nameColumn, x => x.Name) + .AddSortable(languageColumn, x => x.Language) + .AddSortable(codecColumn, x => x.Codec) + .AddSortable(defaultColumn, x => x.IsDefault) + .AddSortable(forcedColumn, x => x.IsForced); + return UiUtil.MakeBorderForControlNoPadding(dataGridTracks); } @@ -141,45 +157,45 @@ private static Border MakeSubtitleView(PickTsTrackViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Rows; + + // No sorter here: the preview shows subtitle cues in subtitle order. + dataGridSubtitle.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Rows, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(TsSubtitleCueDisplay.Number)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(TsSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(TsSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, + Width = new GridLength(90), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.TextOrImage, - IsReadOnly = true, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new FuncDataTemplate((item, _) => { var stackPanel = new StackPanel @@ -216,8 +232,7 @@ private static Border MakeSubtitleView(PickTsTrackViewModel vm) return stackPanel; }) }, - }, - }; + }); return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); } diff --git a/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageViewModel.cs b/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageViewModel.cs index b33c91b64d2..9664e315fb9 100644 --- a/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageViewModel.cs +++ b/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageViewModel.cs @@ -21,7 +21,7 @@ public partial class PickVobSubLanguageViewModel : ObservableObject [ObservableProperty] private ObservableCollection _rows; public Window? Window { get; set; } - public DataGrid LanguagesGrid { get; set; } + public TableView LanguagesGrid { get; set; } public bool OkPressed { get; private set; } public string WindowTitle { get; private set; } @@ -37,7 +37,7 @@ public PickVobSubLanguageViewModel() { Languages = new ObservableCollection(); Rows = new ObservableCollection(); - LanguagesGrid = new DataGrid(); + LanguagesGrid = new TableView(); WindowTitle = string.Empty; SelectedLanguageString = string.Empty; _streamIdDictionary = new Dictionary>(); @@ -101,14 +101,14 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs e) Cancel(); e.Handled = true; } - else if (e.Key == Key.Enter && LanguagesGrid.IsFocused) + else if (e.Key == Key.Enter && LanguagesGrid.IsKeyboardFocusWithin) { Ok(); e.Handled = true; } } - internal void DataGridLanguagesSelectionChanged(object? sender, SelectionChangedEventArgs e) + internal void LanguagesGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { LanguageChanged(); } @@ -152,7 +152,11 @@ internal void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { LanguagesGrid.SelectedIndex = index; - LanguagesGrid.ScrollIntoView(LanguagesGrid.SelectedItem, null); + if (LanguagesGrid.SelectedItem is { } selectedItem) + { + LanguagesGrid.ScrollIntoView(selectedItem); + } + LanguageChanged(); }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageWindow.cs b/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageWindow.cs index 93e99d87206..ae166d96167 100644 --- a/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageWindow.cs +++ b/src/ui/Features/Shared/PickVobSubLanguage/PickVobSubLanguageWindow.cs @@ -62,56 +62,62 @@ public PickVobSubLanguageWindow(PickVobSubLanguageViewModel vm) Dispatcher.UIThread.InvokeAsync(() => { vm.SelectAndScrollToRow(0); - vm.LanguagesGrid.Focus(); + TableViewExtras.FocusRow(vm.LanguagesGrid); }, DispatcherPriority.Input); }; } private static Border MakeLanguagesView(PickVobSubLanguageViewModel vm) { - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Languages; + + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star, so the + // stream-id and count columns get fixed widths (Language keeps the star). + var streamIdColumn = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Languages, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(VobSubLanguageDisplay.StreamIdHex)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(VobSubLanguageDisplay.Language)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Count, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(VobSubLanguageDisplay.Count)), - IsReadOnly = true, - }, - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(VobSubLanguageDisplay.StreamIdHex)), + Width = new GridLength(80), }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedLanguage))); - dataGrid.SelectionChanged += vm.DataGridLanguagesSelectionChanged; + var languageColumn = new SeTableViewColumn + { + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(VobSubLanguageDisplay.Language)), + Width = new GridLength(1, GridUnitType.Star), + }; + var countColumn = new SeTableViewColumn + { + Header = Se.Language.General.Count, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(VobSubLanguageDisplay.Count)), + Width = new GridLength(80), + }; + + dataGrid.Columns.Add(streamIdColumn); + dataGrid.Columns.Add(languageColumn); + dataGrid.Columns.Add(countColumn); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedLanguage))); + dataGrid.SelectionChanged += vm.LanguagesGridSelectionChanged; dataGrid.DoubleTapped += (_, _) => vm.OkCommand.Execute(null); vm.LanguagesGrid = dataGrid; + // Language list order is presentation-only (OK uses the selected item), so the + // in-place header sorter is safe. The hex column sorts by the numeric stream id. + new TableViewHeaderSorter(dataGrid) + .AddSortable(streamIdColumn, x => x.StreamId) + .AddSortable(languageColumn, x => x.Language) + .AddSortable(countColumn, x => x.Count); + return UiUtil.MakeBorderForControlNoPadding(dataGrid); } @@ -119,46 +125,46 @@ private static Border MakePreviewView(PickVobSubLanguageViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGrid = new DataGrid + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Rows; + + // No sorter here: the preview shows subtitle cues in subtitle order (the old + // DataGrid had sorting disabled too). + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = false, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Rows, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(VobSubLanguageCueDisplay.Number)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(VobSubLanguageCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(VobSubLanguageCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, + Width = new GridLength(90), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Image, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new Avalonia.Controls.Templates.FuncDataTemplate((item, _) => { if (item.Image == null) @@ -175,8 +181,7 @@ private static Border MakePreviewView(PickVobSubLanguageViewModel vm) }; }), }, - }, - }; + }); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Ssa/SsaAttachmentsWindow.cs b/src/ui/Features/Ssa/SsaAttachmentsWindow.cs index 5706e52469c..58a993a4a1a 100644 --- a/src/ui/Features/Ssa/SsaAttachmentsWindow.cs +++ b/src/ui/Features/Ssa/SsaAttachmentsWindow.cs @@ -92,58 +92,42 @@ private static Border MakeLeftView(SsaAttachmentsViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, }; - var dataGrid = new DataGrid + // No header sorting: the attachment order is written back to the subtitle + // footer ([Fonts]/[Graphics] sections) in list order on OK, so the collection + // order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Attachments; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Attachments, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SsaAttachmentItem.FileName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Type, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SsaAttachmentItem.Category)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SsaAttachmentItem.Size)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedAttachment)) { Source = vm }); + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SsaAttachmentItem.FileName)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Type, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SsaAttachmentItem.Category)), + Width = new GridLength(140), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SsaAttachmentItem.Size)), + Width = new GridLength(100), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedAttachment)) { Source = vm }); dataGrid.SelectionChanged += vm.DataGridSelectionChanged; dataGrid.KeyDown += vm.AttachmentsDataGridKeyDown; - dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => - { - if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) - { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); - e.Handled = true; - } - }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + TableViewExtras.AttachHomeEndNavigation(dataGrid); var flyout = new MenuFlyout(); flyout.Opening += vm.AttachmentsContextMenuOpening; diff --git a/src/ui/Features/Ssa/SsaStylesViewModel.cs b/src/ui/Features/Ssa/SsaStylesViewModel.cs index 0a2a20b3055..50310a8da6e 100644 --- a/src/ui/Features/Ssa/SsaStylesViewModel.cs +++ b/src/ui/Features/Ssa/SsaStylesViewModel.cs @@ -49,8 +49,8 @@ public partial class SsaStylesViewModel : ObservableObject, IClosingCleanup public Window? Window { get; set; } public bool OkPressed { get; private set; } public string Header { get; set; } - public DataGrid FileStyleGrid { get; set; } - public DataGrid StorageStyleGrid { get; set; } + public TableView FileStyleGrid { get; set; } + public TableView StorageStyleGrid { get; set; } public Subtitle ResultSubtitle => _subtitle; private readonly IFileHelper _fileHelper; @@ -73,8 +73,8 @@ public SsaStylesViewModel(IFileHelper fileHelper, IWindowService windowService) BorderTypes = new ObservableCollection(BorderStyleItem.List()); SelectedBorderType = BorderTypes[0]; CurrentTitle = string.Empty; - FileStyleGrid = new DataGrid(); - StorageStyleGrid = new DataGrid(); + FileStyleGrid = new TableView(); + StorageStyleGrid = new TableView(); Header = string.Empty; _subtitle = new Subtitle(); @@ -216,7 +216,7 @@ private void FileNew() [RelayCommand] private void FileRemove() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -240,7 +240,7 @@ private void FileRemoveAll() [RelayCommand] private void FilesDuplicate() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -302,7 +302,7 @@ private async Task FileExport() [RelayCommand] private void FileCopyToStorage() { - var selectedItems = FileStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = FileStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -418,7 +418,7 @@ private void StorageNew() [RelayCommand] private void StorageRemove() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -474,7 +474,7 @@ private void StorageRemove() } } - StorageStyleGrid.Focus(); + TableViewExtras.FocusRow(StorageStyleGrid); }); } @@ -487,7 +487,7 @@ private void StorageRemoveAll() [RelayCommand] private void StorageDuplicate() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -547,7 +547,7 @@ private async Task StorageExport() [RelayCommand] private void StorageCopyToFiles() { - var selectedItems = StorageStyleGrid.SelectedItems.Cast().ToList(); + var selectedItems = StorageStyleGrid.SelectedItems?.Cast().ToList() ?? new List(); if (Window == null || selectedItems.Count == 0) { return; @@ -644,7 +644,7 @@ public void Initialize( } IsFileStyleSelected = SelectedFileStyle != null; - IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems.Count == 1; + IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems?.Count == 1; _timerUpdatePreview.Start(); } @@ -895,7 +895,7 @@ private void SwitchToFileStyle() CurrentTitle = Se.Language.Assa.StylesInFile; SelectedBorderType = selectedStyle?.BorderStyle ?? BorderTypes[0]; IsFileStyleSelected = selectedStyle != null; - IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems.Count == 1; + IsTakeUsagesFromVisible = FileStyleGrid.SelectedItems?.Count == 1; } private void SwitchToStorageStyle() @@ -905,8 +905,8 @@ private void SwitchToStorageStyle() CurrentTitle = Se.Language.Assa.StylesSaved; SelectedBorderType = selectedStyle?.BorderStyle ?? BorderTypes[0]; IsStorageStyleSelected = selectedStyle != null; - IsSetStyleAsDefaultVisible = StorageStyleGrid.SelectedItems.Count == 1; - IsCopyToFileStylesVisible = StorageStyleGrid.SelectedItems.Count > 0; + IsSetStyleAsDefaultVisible = StorageStyleGrid.SelectedItems?.Count == 1; + IsCopyToFileStylesVisible = StorageStyleGrid.SelectedItems?.Count > 0; } internal void BorderTypeChanged(object? sender, SelectionChangedEventArgs e) @@ -976,7 +976,7 @@ private void DeleteFileStyle(StyleDisplay? selectedStyle) UpdateUsages(); } - FileStyleGrid.Focus(); + TableViewExtras.FocusRow(FileStyleGrid); }); } @@ -1026,7 +1026,7 @@ private void DeleteFileStyles(List selectedStyles) } UpdateUsages(); - FileStyleGrid.Focus(); + TableViewExtras.FocusRow(FileStyleGrid); }); } diff --git a/src/ui/Features/Ssa/SsaStylesWindow.cs b/src/ui/Features/Ssa/SsaStylesWindow.cs index 4503548141e..a8c4ad8716d 100644 --- a/src/ui/Features/Ssa/SsaStylesWindow.cs +++ b/src/ui/Features/Ssa/SsaStylesWindow.cs @@ -112,67 +112,50 @@ private static Border MakeFileStyles(SsaStylesViewModel vm) var label = UiUtil.MakeLabel(Se.Language.Assa.StylesInFile).WithBold(); - var dataGrid = new DataGrid + // No header sorting: SSA styles are written to the file header in list + // order on OK, so the collection order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.FileStyles; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.FileStyles, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Usages, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.UsageCount)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedFileStyle)) { Source = vm }); + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontName)), + Width = new GridLength(150), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontSize, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontSize)), + Width = new GridLength(90), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Usages, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.UsageCount)), + Width = new GridLength(90), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFileStyle)) { Source = vm }); dataGrid.SelectionChanged += vm.FileStylesChanged; dataGrid.GotFocus += vm.FileStylesGotFocus; dataGrid.KeyDown += vm.FileStylesKeyDown; - dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => - { - if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) - { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); - e.Handled = true; - } - }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + TableViewExtras.AttachHomeEndNavigation(dataGrid); vm.FileStyleGrid = dataGrid; var flyout = new MenuFlyout(); @@ -258,52 +241,46 @@ private static Border MakeStorageStyles(SsaStylesViewModel vm) var label = UiUtil.MakeLabel(Se.Language.Assa.StylesSaved).WithBold(); - var dataGrid = new DataGrid + // No header sorting: the storage style order is persisted to settings in + // list order on OK, so the collection order is not presentation-only. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.StorageStyles; + + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.StorageStyles, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.IsDefault, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(StyleDisplay.IsDefault)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedStorageStyle)) { Source = vm }); + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontName)), + Width = new GridLength(150), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FontSize, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.FontSize)), + Width = new GridLength(90), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.IsDefault, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(StyleDisplay.IsDefault)), + Width = new GridLength(90), + }); + + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStorageStyle)) { Source = vm }); dataGrid.SelectionChanged += vm.StorageStylesChanged; dataGrid.GotFocus += vm.StorageStylesGotFocus; vm.StorageStyleGrid = dataGrid; diff --git a/src/ui/Features/Sync/PointSync/PointSyncWindow.cs b/src/ui/Features/Sync/PointSync/PointSyncWindow.cs index f9a41e062e3..8e1e6ea95b3 100644 --- a/src/ui/Features/Sync/PointSync/PointSyncWindow.cs +++ b/src/ui/Features/Sync/PointSync/PointSyncWindow.cs @@ -2,12 +2,14 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Features.Sync.PointSyncViaOther; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; +using System.Collections; namespace Nikse.SubtitleEdit.Features.Sync.PointSync; @@ -84,27 +86,28 @@ private static Control MakeControlView(PointSyncViewModel vm) Margin = new Thickness(0, 60, 0, 0), }; - var dataGrid = new DataGrid + // The DataGrid this replaces hid its header (HeadersVisibility.None); TableView + // has no such switch, so the single column's header now doubles as the panel title. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.CanUserResizeColumns = false; // single star column, nothing to resize + // Fixed width: this panel sits in an Auto-sized outer column, and a TableView + // with a star column measured without a width constraint demands more than the + // whole window (star columns have no content-based size), squeezing the + // subtitle grids to slivers and overflowing the right edge. + dataGrid.Width = 280; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.SyncPoints; + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - DataContext = vm, - ItemsSource = vm.SyncPoints, - HeadersVisibility = DataGridHeadersVisibility.None, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.Sync.SyncPoints, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SyncPoint.Text)), - IsReadOnly = true, - }, - }, - }; + Header = Se.Language.Sync.SyncPoints, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SyncPoint.Text)), + Width = new GridLength(1, GridUnitType.Star), + }); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSyncPoint))); - UiUtil.AttachHomeEndNavigation(dataGrid); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSyncPoint))); + TableViewExtras.AttachHomeEndNavigation(dataGrid); var menuItemDelete = new MenuItem { @@ -138,54 +141,53 @@ private static Border MakeSubtitleView(PointSyncViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGrid = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): the lines are shown in timeline order, which the sync-point logic + // relies on. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Subtitles; + dataGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle))); - UiUtil.AttachHomeEndNavigation(dataGrid); + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle))); + TableViewExtras.AttachHomeEndNavigation(dataGrid); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } + } \ No newline at end of file diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs index 3642fc91aad..e5789d2eccb 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs @@ -2,8 +2,10 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using System.Collections; using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -88,26 +90,27 @@ private static Control MakeControlView(PointSyncViaOtherViewModel vm) Margin = new Thickness(0, 60, 0, 0), }; - var dataGrid = new DataGrid + // The DataGrid this replaces hid its header (HeadersVisibility.None); TableView + // has no such switch, so the single column's header now doubles as the panel title. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.CanUserResizeColumns = false; // single star column, nothing to resize + // Fixed width: this panel sits in an Auto-sized outer column, and a TableView + // with a star column measured without a width constraint demands more than the + // whole window (star columns have no content-based size), squeezing the + // subtitle grids to slivers and overflowing the right edge. + dataGrid.Width = 280; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.SyncPoints; + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - DataContext = vm, - ItemsSource = vm.SyncPoints, - HeadersVisibility = DataGridHeadersVisibility.None, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.Sync.SyncPoints, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SyncPoint.Text)), - IsReadOnly = true, - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSyncPoint))); - UiUtil.AttachHomeEndNavigation(dataGrid); + Header = Se.Language.Sync.SyncPoints, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SyncPoint.Text)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSyncPoint))); + TableViewExtras.AttachHomeEndNavigation(dataGrid); var menuItemDelete = new MenuItem { @@ -171,46 +174,43 @@ private static Grid MakeSubtitleView(PointSyncViaOtherViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGrid = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): both grids show lines in timeline order, which the sync-point + // matching relies on. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Subtitles; + dataGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle))); - UiUtil.AttachHomeEndNavigation(dataGrid); + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSubtitle))); + TableViewExtras.AttachHomeEndNavigation(dataGrid); grid.Add(panelHeader, 0); grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); @@ -270,55 +270,49 @@ private static Grid MakeSubtitleOtherView(PointSyncViaOtherViewModel vm) panelOtherHeader.Add(buttonFindTextOther, 0, 1); var fullTimeConverter = new TimeSpanToDisplayFullConverter(); - var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Othersubtitles; + dataGridSubtitle.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Othersubtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; - dataGridSubtitle.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedOtherSubtitle))); - UiUtil.AttachHomeEndNavigation(dataGridSubtitle); + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + dataGridSubtitle.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedOtherSubtitle))); + TableViewExtras.AttachHomeEndNavigation(dataGridSubtitle); // Clicking a line in the left grid scrolls this grid to the matching time (#12529) // without touching its selection. - vm.ScrollOtherToLine = line => dataGridSubtitle.ScrollIntoView(line, null); + vm.ScrollOtherToLine = line => dataGridSubtitle.ScrollIntoView(line); grid.Add(panelOtherHeader, 0); grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle), 1); return grid; } + } \ No newline at end of file diff --git a/src/ui/Features/Tools/AiReview/AiReviewWindow.cs b/src/ui/Features/Tools/AiReview/AiReviewWindow.cs index 49ca30efe31..19a49d82bf9 100644 --- a/src/ui/Features/Tools/AiReview/AiReviewWindow.cs +++ b/src/ui/Features/Tools/AiReview/AiReviewWindow.cs @@ -198,24 +198,22 @@ public AiReviewWindow(AiReviewViewModel vm) chipsBar.Add(warningNote, 0, 1); // ---------- suggestions grid ---------- - var dataGrid = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): suggestions are fix previews in subtitle order. The DataGrid-era + // DataGridCheckboxMultiSelect is replaced by native extended selection plus + // TableViewExtras.AddSpaceToggle for the Space-toggles-checkbox piece. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Suggestions; + dataGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Suggestions, - IsReadOnly = false, - Columns = - { - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => new Border { Background = Brushes.Transparent, @@ -228,19 +226,21 @@ public AiReviewWindow(AiReviewViewModel vm) HorizontalAlignment = HorizontalAlignment.Center, }, }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), + Width = new GridLength(80), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(ReviewSuggestionItem.Number)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.Tools.FixCommonErrors.Action, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { var panel = new StackPanel @@ -299,13 +299,13 @@ public AiReviewWindow(AiReviewViewModel vm) Child = panel, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), + Width = new GridLength(150), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { if (item == null) @@ -321,13 +321,13 @@ public AiReviewWindow(AiReviewViewModel vm) Child = beforeBlock, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { if (item == null) @@ -343,14 +343,12 @@ public AiReviewWindow(AiReviewViewModel vm) Child = afterBlock, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; + }); AutomationProperties.SetName(dataGrid, l.Title); - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSuggestion))); - _ = new DataGridCheckboxMultiSelect(dataGrid, + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSuggestion))); + TableViewExtras.AddSpaceToggle(dataGrid, item => item.IsSelected, (item, v) => item.IsSelected = v); var borderGrid = UiUtil.MakeBorderForControlNoPadding(dataGrid); diff --git a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs index aa132c8a062..ca30ed9908d 100644 --- a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs +++ b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs @@ -137,56 +137,54 @@ private static Grid MakeFixesView(ApplyDurationLimitsViewModel vm) .WithMarginTop(10) .WithMarginLeft(10); - var dataGrid = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): the fixes are previews in subtitle order. The DataGrid-era + // DataGridCheckboxMultiSelect is replaced by native extended selection plus + // TableViewExtras.AddSpaceToggle for the Space-toggles-checkbox piece. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Fixes; + dataGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Fixes, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn - { - Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border + Header = Se.Language.General.Apply, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border + { + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - Focusable = false, - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(ApplyDurationLimitItem.Apply)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - Binding = new Binding(nameof(ApplyDurationLimitItem.Number)), - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Fix, - Binding = new Binding(nameof(ApplyDurationLimitItem.Fix)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, - }, + Focusable = false, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(ApplyDurationLimitItem.Apply)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + Width = new GridLength(80), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; - _ = new DataGridCheckboxMultiSelect(dataGrid, + new SeTableViewColumn + { + Header = Se.Language.General.NumberSymbol, + Binding = new Binding(nameof(ApplyDurationLimitItem.Number)), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(60), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Fix, + Binding = new Binding(nameof(ApplyDurationLimitItem.Fix)), + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + }); + TableViewExtras.AddSpaceToggle(dataGrid, item => item.Apply, (item, v) => item.Apply = v); grid.Add(labelFixesAvailable, 0); @@ -221,61 +219,60 @@ private static Grid MakeCannotFixView(ApplyDurationLimitsViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGrid = new DataGrid + // No header-click sorting here either: lines that cannot be fixed, in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Subtitles; + dataGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - Width = new DataGridLength(120), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, - Width = new DataGridLength(120), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, - }, + Header = Se.Language.General.NumberSymbol, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; + new SeTableViewColumn + { + Header = Se.Language.General.Show, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(120), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(120), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + Binding = new Binding(nameof(SubtitleLineViewModel.Text)), + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }, + }); dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { var target = e.Key == Key.Home ? items[0] : items[^1]; + if (target == null) + { + return; + } + dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + dataGrid.ScrollIntoView(target); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs b/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs index 8c8eee545b6..1fabc7c2439 100644 --- a/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs +++ b/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs @@ -75,67 +75,69 @@ private static Border MakeSubtitleView(ApplyMinGapViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): gap-change previews in subtitle order. + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Subtitles; + dataGridSubtitle.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ApplyMinGapItem.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ApplyMinGapItem.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ApplyMinGapItem.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ApplyMinGapItem.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.Tools.BridgeGaps.GapChange, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ApplyMinGapItem.InfoText)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ApplyMinGapItem.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ApplyMinGapItem.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ApplyMinGapItem.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ApplyMinGapItem.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + new SeTableViewColumn + { + Header = Se.Language.Tools.BridgeGaps.GapChange, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ApplyMinGapItem.InfoText)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); dataGridSubtitle.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGridSubtitle.ItemsSource is IList items && items.Count > 0) { var target = e.Key == Key.Home ? items[0] : items[^1]; + if (target == null) + { + return; + } + dataGridSubtitle.SelectedItem = target; - dataGridSubtitle.ScrollIntoView(target, null); + dataGridSubtitle.ScrollIntoView(target); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs index 161fe2f473c..986718da21f 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs @@ -64,56 +64,53 @@ public BatchConvertFixCommonErrorsSettingsWindow(BatchConvertFixCommonErrorsSett private static Border MakeRulesView(BatchConvertFixCommonErrorsSettingsViewModel vm) { - var rulesGrid = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): the fix rules are a settings checklist in their fixed rule order. + // The DataGrid-era DataGridCheckboxMultiSelect is replaced by native extended + // selection plus TableViewExtras.AddSpaceToggle for the Space-toggles-checkbox piece. + var rulesGrid = TableViewExtras.MakeTableView(); + rulesGrid.Width = double.NaN; + rulesGrid.Height = double.NaN; + rulesGrid[!TableView.ItemsSourceProperty] = new Binding($"{nameof(vm.SelectedProfile)}.{nameof(ProfileDisplayItem.FixRules)}"); + rulesGrid.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - [!DataGrid.ItemsSourceProperty] = new Binding($"{nameof(vm.SelectedProfile)}.{nameof(ProfileDisplayItem.FixRules)}"), - IsReadOnly = false, - Columns = + new SeTableViewColumn { - new DataGridTemplateColumn - { - Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => - new Border + Header = Se.Language.General.Enabled, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => + new Border + { + Background = Brushes.Transparent, // Prevents highlighting + Padding = new Thickness(4), + Child = new CheckBox { - Background = Brushes.Transparent, // Prevents highlighting - Padding = new Thickness(4), - Child = new CheckBox - { - Focusable = false, - [!ToggleButton.IsCheckedProperty] = new Binding(nameof(FixRuleDisplayItem.IsSelected)), - HorizontalAlignment = HorizontalAlignment.Center - } - }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FixRuleDisplayItem.Name)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Example, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(FixRuleDisplayItem.Example)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star) - }, + Focusable = false, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(FixRuleDisplayItem.IsSelected)), + HorizontalAlignment = HorizontalAlignment.Center + } + }), + Width = new GridLength(80), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; - _ = new DataGridCheckboxMultiSelect(rulesGrid, + new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FixRuleDisplayItem.Name)), + Width = new GridLength(340), // content-sized (Auto) on the DataGrid; rule names are long + }, + new SeTableViewColumn + { + Header = Se.Language.General.Example, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(FixRuleDisplayItem.Example)), + Width = new GridLength(1, GridUnitType.Star), + }, + }); + TableViewExtras.AddSpaceToggle(rulesGrid, item => item.IsSelected, (item, v) => item.IsSelected = v); return UiUtil.MakeBorderForControl(rulesGrid); diff --git a/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs b/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs index 4e7db0bd635..69c6791cd69 100644 --- a/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs +++ b/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs @@ -89,67 +89,69 @@ private Border MakeSubtitleView(BridgeGapsViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): gap-change previews in subtitle order. + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Subtitles; + dataGridSubtitle.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BridgeGapDisplayItem.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BridgeGapDisplayItem.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BridgeGapDisplayItem.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BridgeGapDisplayItem.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.Tools.BridgeGaps.GapChange, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BridgeGapDisplayItem.InfoText)), - IsReadOnly = true, - Width = new DataGridLength(120), - }, + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BridgeGapDisplayItem.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star }, - }; + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BridgeGapDisplayItem.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BridgeGapDisplayItem.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BridgeGapDisplayItem.Text)), + Width = new GridLength(1, GridUnitType.Star), + }, + new SeTableViewColumn + { + Header = Se.Language.Tools.BridgeGaps.GapChange, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BridgeGapDisplayItem.InfoText)), + Width = new GridLength(120), + }, + }); dataGridSubtitle.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGridSubtitle.ItemsSource is IList items && items.Count > 0) { var target = e.Key == Key.Home ? items[0] : items[^1]; + if (target == null) + { + return; + } + dataGridSubtitle.SelectedItem = target; - dataGridSubtitle.ScrollIntoView(target, null); + dataGridSubtitle.ScrollIntoView(target); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs b/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs index f4d8f4f74ce..847226ff85c 100644 --- a/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs +++ b/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs @@ -90,101 +90,98 @@ private static Border MakeSubtitleView(ChangeFormattingViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); var colorConverter = new TextWithSubtitleSyntaxHighlightingConverter(); - var dataGridSubtitle = new DataGrid + // No header-click sorting (the DataGrid's CanUserSortColumns is not carried + // over): before/after formatting previews in subtitle order. + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Subtitles; + dataGridSubtitle.Columns.AddRange(new[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = + new SeTableViewColumn { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ChangeFormattingDisplayItem.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ChangeFormattingDisplayItem.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(ChangeFormattingDisplayItem.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTemplateColumn + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ChangeFormattingDisplayItem.Number)), + Width = new GridLength(60), // content-sized (Auto) on the DataGrid; TableView treats Auto as star + }, + new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ChangeFormattingDisplayItem.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(115), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(ChangeFormattingDisplayItem.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), + }, + new SeTableViewColumn + { + Header = Se.Language.General.Before, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), + CellTemplate = new FuncDataTemplate((value, nameScope) => { - Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTemplate = new FuncDataTemplate((value, nameScope) => + var border = new Border + { + Padding = new Thickness(4, 2), + }; + + var textBlock = new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.NoWrap, + [!TextBlock.InlinesProperty] = new Binding(nameof(ChangeFormattingDisplayItem.Text)) { Converter = colorConverter, Mode = BindingMode.OneWay }, + }; + + if (!string.IsNullOrEmpty(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName)) { - var border = new Border - { - Padding = new Thickness(4, 2), - }; - - var textBlock = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.NoWrap, - [!TextBlock.InlinesProperty] = new Binding(nameof(ChangeFormattingDisplayItem.Text)) { Converter = colorConverter, Mode = BindingMode.OneWay }, - }; - - if (!string.IsNullOrEmpty(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName)) - { - textBlock.FontFamily = new FontFamily(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName); - } - - border.Child = textBlock; - return border; - }) - }, - new DataGridTemplateColumn + textBlock.FontFamily = new FontFamily(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName); + } + + border.Child = textBlock; + return border; + }) + }, + new SeTableViewColumn + { + Header = Se.Language.General.After, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), + CellTemplate = new FuncDataTemplate((value, nameScope) => { - Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTemplate = new FuncDataTemplate((value, nameScope) => + var border = new Border { - var border = new Border - { - Padding = new Thickness(4, 2), - }; - - var textBlock = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.NoWrap, - [!TextBlock.InlinesProperty] = new Binding(nameof(ChangeFormattingDisplayItem.NewText)) { Converter = colorConverter, Mode = BindingMode.OneWay }, - }; - - if (!string.IsNullOrEmpty(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName)) - { - textBlock.FontFamily = new FontFamily(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName); - } - - border.Child = textBlock; - return border; - }) - }, + Padding = new Thickness(4, 2), + }; + + var textBlock = new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.NoWrap, + [!TextBlock.InlinesProperty] = new Binding(nameof(ChangeFormattingDisplayItem.NewText)) { Converter = colorConverter, Mode = BindingMode.OneWay }, + }; + + if (!string.IsNullOrEmpty(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName)) + { + textBlock.FontFamily = new FontFamily(Se.Settings.Appearance.SubtitleTextBoxAndGridFontName); + } + + border.Child = textBlock; + return border; + }) }, - }; + }); return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); } diff --git a/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs b/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs index 635c12eddf4..d75f24ac915 100644 --- a/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs +++ b/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs @@ -143,26 +143,21 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) var shortTimeConverter = new TimeSpanToDisplayShortConverter(); var colorConverter = new TextWithSubtitleSyntaxHighlightingConverter(); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // subtitle lines in timeline order. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Subtitles; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Subtitles, - Columns = - { - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = false, - Width = new DataGridLength(55), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(55), CellTemplate = new FuncDataTemplate((_, _) => new CheckBox { @@ -172,26 +167,29 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) [!CheckBox.IsCheckedProperty] = new Binding(nameof(ConvertActorsDisplayItem.IsChecked)) { Mode = BindingMode.TwoWay }, }), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(ConvertActorsDisplayItem.Number)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(ConvertActorsDisplayItem.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, + Width = new GridLength(120), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new FuncDataTemplate((_, _) => { var border = new Border { Padding = new Thickness(4, 2) }; @@ -209,12 +207,12 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) return border; }), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(1, GridUnitType.Star), CellTemplate = new FuncDataTemplate((_, _) => { var border = new Border { Padding = new Thickness(4, 2) }; @@ -232,9 +230,11 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) return border; }), }, - }, - }; - _ = new DataGridCheckboxMultiSelect(dataGrid, + }); + + // Extended selection is native ListBox behavior on TableView; only the + // Space-toggles-checkbox piece of the old CheckboxMultiSelect needs wiring. + TableViewExtras.AddSpaceToggle(dataGrid, item => item.IsChecked, (item, v) => item.IsChecked = v); return UiUtil.MakeBorderForControlNoPadding(dataGrid); diff --git a/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs b/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs index c0ed642eff4..b33311e4a16 100644 --- a/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs +++ b/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs @@ -106,22 +106,19 @@ private static Border MakeSettingsView(FixNetflixErrorsViewModel vm) } }; - // Grid with list of checks - var dataGrid = new DataGrid - { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - [!DataGrid.ItemsSourceProperty] = new Binding(nameof(vm.Checks)) - }; + // Grid with list of checks. Sorting dropped in the DataGrid -> TableView + // conversion: the checks run in list order (RunChecks gets them in collection + // order), so the list must not be reordered. + // Stretch (the MakeTableView default) instead of the DataGrid's Left/Top: the + // star-sized Name column needs the control to fill its fixed-width grid cell. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid[!TableView.ItemsSourceProperty] = new Binding(nameof(vm.Checks)); - dataGrid.Columns.Add(new DataGridTemplateColumn + dataGrid.Columns.Add(new SeTableViewColumn { Header = Se.Language.General.Enabled, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { var cb = new CheckBox @@ -138,18 +135,22 @@ private static Border MakeSettingsView(FixNetflixErrorsViewModel vm) Child = cb }; }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) }); - dataGrid.Columns.Add(new DataGridTextColumn + dataGrid.Columns.Add(new SeTableViewColumn { Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(NetflixCheckDisplayItem.Name)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + Width = new GridLength(1, GridUnitType.Star) }); - _ = new DataGridCheckboxMultiSelect(dataGrid, + + // Extended selection is native ListBox behavior on TableView; only the + // Space-toggles-checkbox piece of the old CheckboxMultiSelect needs wiring. + TableViewExtras.AddSpaceToggle(dataGrid, item => item.IsSelected, (item, v) => item.IsSelected = v); var grid = new Grid @@ -173,24 +174,20 @@ private static Border MakeSettingsView(FixNetflixErrorsViewModel vm) private Border MakeFixesView(FixNetflixErrorsViewModel vm) { - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // fixes in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = _vm; + dataGrid.ItemsSource = _vm.Fixes; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = _vm, - ItemsSource = _vm.Fixes, - Columns = - { - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { var cb = new CheckBox @@ -208,19 +205,22 @@ private Border MakeFixesView(FixNetflixErrorsViewModel vm) Child = cb, }; }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(FixNetflixErrorsItem.IndexDisplay)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { if (item == null) @@ -236,13 +236,13 @@ private Border MakeFixesView(FixNetflixErrorsViewModel vm) Child = beforeBlock, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { if (item == null) @@ -258,23 +258,32 @@ private Border MakeFixesView(FixNetflixErrorsViewModel vm) Child = afterBlock, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Reason, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(FixNetflixErrorsItem.Reason)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(_vm.SelectedFix))); - _ = new DataGridCheckboxMultiSelect(dataGrid, - item => item.Apply, (item, v) => item.Apply = v, - canToggle: item => item.CanBeFixed); + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(_vm.SelectedFix))); + + // Extended selection is native ListBox behavior on TableView; only the + // Space-toggles-checkbox piece of the old CheckboxMultiSelect needs wiring. + // Non-fixable rows count as "checked" so they never block the all-checked + // toggle, and the setter leaves them alone (the old helper's canToggle). + TableViewExtras.AddSpaceToggle(dataGrid, + item => !item.CanBeFixed || item.Apply, + (item, v) => + { + if (item.CanBeFixed) + { + item.Apply = v; + } + }); return UiUtil.MakeBorderForControlNoPadding(dataGrid); } diff --git a/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesViewModel.cs b/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesViewModel.cs index f2f854aa963..92db6a84d69 100644 --- a/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesViewModel.cs +++ b/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesViewModel.cs @@ -419,7 +419,7 @@ private async Task Revert(int idx, string message) await MessageBox.Show(Window, "", message); } - internal void DataGridKeyDown(object? sender, KeyEventArgs e) + internal void GridKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Delete && SelectedJoinItem != null) { diff --git a/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesWindow.cs b/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesWindow.cs index 4aedea2cd4e..5e589dcda1e 100644 --- a/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesWindow.cs +++ b/src/ui/Features/Tools/JoinSubtitles/JoinSubtitlesWindow.cs @@ -78,62 +78,59 @@ private static Border MakeFilesView(JoinSubtitlesViewModel vm) var fullTimeConverter = new TimeSpanToDisplayFullConverter(); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the join is produced + // by iterating this list in order (the VM sorts it by start time itself), so the + // list must not be reordered by clicking a header. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.JoinItems; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.JoinItems, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NoSymbolLines, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(JoinDisplayItem.Lines)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.StartTime, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(JoinDisplayItem.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.EndTime, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(JoinDisplayItem.EndTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(JoinDisplayItem.FileName)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedJoinItem)) { Source = vm }); - dataGrid.KeyDown += vm.DataGridKeyDown; + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedJoinItem)) { Source = vm }); + dataGrid.KeyDown += vm.GridKeyDown; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs index 8b08a00ba45..f33b3ecdb9c 100644 --- a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs +++ b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs @@ -125,45 +125,41 @@ private static Grid MakeFixesView(MergeShortLinesViewModel vm) .WithMarginTop(10) .WithMarginLeft(10); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // merge candidates in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Fixes; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Fixes, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, Binding = new Binding(nameof(MergeShortLinesItem.Number)), - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Fix, Binding = new Binding(nameof(MergeShortLinesItem.Fix)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, }, - }, - }; + }); dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs index 727993e3f9b..f05b13a26f4 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs @@ -27,7 +27,7 @@ public partial class MergeSameTextViewModel : ObservableObject, IClosingCleanup public bool OkPressed { get; private set; } public List ResultSubtitles { get; set; } - public DataGrid SubtitleGrid { get; set; } + public TableView SubtitleGrid { get; set; } private readonly System.Timers.Timer _timerUpdatePreview; private volatile bool _isClosing; @@ -39,7 +39,7 @@ public MergeSameTextViewModel() MergeItems = new ObservableCollection(); MergeSubtitles = new ObservableCollection(); ResultSubtitles = new List(); - SubtitleGrid = new DataGrid(); + SubtitleGrid = new TableView(); LoadSettings(); @@ -266,20 +266,21 @@ private void Cancel() Window?.Close(); } - internal void DataGridMergeItemChanged(object? sender, SelectionChangedEventArgs e) + internal void MergeItemChanged(object? sender, SelectionChangedEventArgs e) { var selected = SelectedMergeItem; - if (selected == null) + var selectedItems = SubtitleGrid.SelectedItems; + if (selected == null || selectedItems == null) { return; } - SubtitleGrid.SelectedItems.Clear(); + selectedItems.Clear(); foreach (var item in MergeSubtitles) { if (item is SubtitleLineViewModel svm && svm.Extra == selected.MergedGroup) { - SubtitleGrid.SelectedItems.Add(item); + selectedItems.Add(item); } } } diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs index 0d9aaa5d11b..e017960539f 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs @@ -81,24 +81,20 @@ private static StackPanel MakeControlsView(MergeSameTextViewModel vm) private static Border MakeMergesView(MergeSameTextViewModel vm) { - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid shows + // merge candidates in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.MergeItems; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.MergeItems, - Columns = - { - new DataGridTemplateColumn - { + new SeTableViewColumn + { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => new Border { @@ -110,41 +106,45 @@ private static Border MakeMergesView(MergeSameTextViewModel vm) HorizontalAlignment = HorizontalAlignment.Center } }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Lines, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.Lines)), - IsReadOnly = true, + Width = new GridLength(110), }, - new DataGridTextColumn + new SeTableViewColumn { + // The merged text is the wide content, so it takes the star width + // (the DataGrid content-sized it and gave Group the star). Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.MergedText)), - IsReadOnly = true, + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Group, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.MergedGroup)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(90), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeItem)) { Source = vm }); - dataGrid.SelectionChanged += vm.DataGridMergeItemChanged; + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeItem)) { Source = vm }); + dataGrid.SelectionChanged += vm.MergeItemChanged; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); @@ -156,69 +156,66 @@ private static Border MakeSubtitlesView(MergeSameTextViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // subtitle lines in timeline order. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.MergeSubtitles; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.MergeSubtitles, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Hide, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Group, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Extra)), - IsReadOnly = true, + Width = new GridLength(90), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeSubtitle)) { Source = vm }); + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeSubtitle)) { Source = vm }); vm.SubtitleGrid = dataGrid; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesViewModel.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesViewModel.cs index 09f9562223f..67b91da8e1e 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesViewModel.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesViewModel.cs @@ -29,7 +29,7 @@ public partial class MergeSameTimeCodesViewModel : ObservableObject, IClosingCle public bool OkPressed { get; private set; } public List ResultSubtitles { get; set; } - public DataGrid SubtitleGrid { get; set; } + public TableView SubtitleGrid { get; set; } private readonly System.Timers.Timer _timerUpdatePreview; private volatile bool _isClosing; @@ -42,7 +42,7 @@ public MergeSameTimeCodesViewModel() MergeItems = new ObservableCollection(); MergeSubtitles = new ObservableCollection(); ResultSubtitles = new List(); - SubtitleGrid = new DataGrid(); + SubtitleGrid = new TableView(); LoadSettings(); @@ -311,20 +311,21 @@ private void Cancel() Window?.Close(); } - internal void DataGridMergeItemChanged(object? sender, SelectionChangedEventArgs e) + internal void MergeItemChanged(object? sender, SelectionChangedEventArgs e) { var selected = SelectedMergeItem; - if (selected == null) + var selectedItems = SubtitleGrid.SelectedItems; + if (selected == null || selectedItems == null) { return; } - SubtitleGrid.SelectedItems.Clear(); + selectedItems.Clear(); foreach (var item in MergeSubtitles) { if (item is SubtitleLineViewModel svm && svm.Extra == selected.MergedGroup) { - SubtitleGrid.SelectedItems.Add(item); + selectedItems.Add(item); } } } diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs index 0e5355acb89..4029ece5c81 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs @@ -87,24 +87,20 @@ private static StackPanel MakeControlsView(MergeSameTimeCodesViewModel vm) private static Border MakeMergesView(MergeSameTimeCodesViewModel vm) { - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid shows + // merge candidates in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.MergeItems; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.MergeItems, - Columns = - { - new DataGridTemplateColumn - { + new SeTableViewColumn + { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => new Border { @@ -116,41 +112,45 @@ private static Border MakeMergesView(MergeSameTimeCodesViewModel vm) HorizontalAlignment = HorizontalAlignment.Center } }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Lines, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.Lines)), - IsReadOnly = true, + Width = new GridLength(110), }, - new DataGridTextColumn + new SeTableViewColumn { + // The merged text is the wide content, so it takes the star width + // (the DataGrid content-sized it and gave Group the star). Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.MergedText)), - IsReadOnly = true, + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Group, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(MergeDisplayItem.MergedGroup)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(90), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeItem)) { Source = vm }); - dataGrid.SelectionChanged += vm.DataGridMergeItemChanged; + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeItem)) { Source = vm }); + dataGrid.SelectionChanged += vm.MergeItemChanged; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); @@ -162,69 +162,66 @@ private static Border MakeSubtitlesView(MergeSameTimeCodesViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // subtitle lines in timeline order. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.MergeSubtitles; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.MergeSubtitles, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Hide, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - Width = new DataGridLength(120, DataGridLengthUnitType.Pixel), + Width = new GridLength(120), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Group, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SubtitleLineViewModel.Extra)), - IsReadOnly = true, + Width = new GridLength(90), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeSubtitle)) { Source = vm }); + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedMergeSubtitle)) { Source = vm }); vm.SubtitleGrid = dataGrid; dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs b/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs index 0fed68023a4..f4475675420 100644 --- a/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs +++ b/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs @@ -303,24 +303,20 @@ private static Border MakeInterjectionsView(RemoveTextForHearingImpairedViewMode private Grid MakeFixesView(RemoveTextForHearingImpairedViewModel vm) { - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // fixes in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = _vm; + dataGrid.ItemsSource = _vm.Fixes; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = _vm, - ItemsSource = _vm.Fixes, - Columns = - { - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => new Border { @@ -333,34 +329,41 @@ private Grid MakeFixesView(RemoveTextForHearingImpairedViewModel vm) HorizontalAlignment = HorizontalAlignment.Center } }), - Width = new DataGridLength(1, DataGridLengthUnitType.Auto) + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(70) }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveItem.IndexDisplay)), - IsReadOnly = true, + Width = new GridLength(60), }, - new DataGridTextColumn + new SeTableViewColumn { + // Content-sized (Auto) on the DataGrid; Before holds subtitle text, + // so it shares the star width with After. Header = Se.Language.General.Before, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveItem.Before)), - IsReadOnly = true, + Width = new GridLength(1, GridUnitType.Star), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.After, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveItem.After)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(_vm.SelectedFix))); - _ = new DataGridCheckboxMultiSelect(dataGrid, + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(_vm.SelectedFix))); + + // Extended selection is native ListBox behavior on TableView; only the + // Space-toggles-checkbox piece of the old CheckboxMultiSelect needs wiring. + TableViewExtras.AddSpaceToggle(dataGrid, item => item.Apply, (item, v) => item.Apply = v); var labelLinesFound = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.LinesFoundText)); diff --git a/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs b/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs index 22eda150bce..743d458c750 100644 --- a/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs +++ b/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs @@ -1,9 +1,13 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Input; using Avalonia.Layout; +using Avalonia.VisualTree; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; +using System.Linq; namespace Nikse.SubtitleEdit.Features.Tools.RemoveUnicodeCharacters; @@ -22,26 +26,22 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) DataContext = vm; var l = Se.Language.Tools.RemoveUnicodeCharacters; - var dataGrid = new DataGrid + + // Sorting dropped in the DataGrid -> TableView conversion: the characters are + // listed in order of first appearance in the subtitle. + var dataGrid = TableViewExtras.MakeTableView(); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Characters; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Characters, - Columns = - { - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Apply, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = false, - Width = new DataGridLength(55), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(55), CellTemplate = new FuncDataTemplate((_, _) => new CheckBox { @@ -51,47 +51,84 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) [!CheckBox.IsCheckedProperty] = new Binding(nameof(RemoveUnicodeCharacterItem.IsChecked)) { Mode = BindingMode.TwoWay }, }), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Character, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveUnicodeCharacterItem.Character)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(90), }, - new DataGridTextColumn + new SeTableViewColumn { Header = "Unicode", - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveUnicodeCharacterItem.CodeDisplay)), - IsReadOnly = true, + Width = new GridLength(100), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Count, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveUnicodeCharacterItem.Count)), - IsReadOnly = true, + Width = new GridLength(70), }, - new DataGridTextColumn + new SeTableViewColumn { + // The DataGrid edited this text column in place; TableView has no cell + // editing, so the cell hosts an always-editable TextBox instead. Header = Se.Language.General.ReplaceWith, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(RemoveUnicodeCharacterItem.ReplaceWith)) { Mode = BindingMode.TwoWay }, - IsReadOnly = false, - Width = new DataGridLength(120), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Width = new GridLength(120), + CellTemplate = new FuncDataTemplate((_, _) => + new TextBox + { + [!TextBox.TextProperty] = new Binding(nameof(RemoveUnicodeCharacterItem.ReplaceWith)) { Mode = BindingMode.TwoWay }, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Stretch, + Margin = new Thickness(2), + }), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Lines, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(RemoveUnicodeCharacterItem.LinesDisplay)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; - _ = new DataGridCheckboxMultiSelect(dataGrid, - item => item.IsChecked, (item, v) => item.IsChecked = v); + }); + + // Extended selection is native ListBox behavior on TableView; only the + // Space-toggles-checkbox piece of the old CheckboxMultiSelect needs wiring. + // Hand-rolled instead of TableViewExtras.AddSpaceToggle because Space typed in + // the ReplaceWith TextBox must keep inserting a space, not toggle checkboxes. + dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => + { + if (e.Key != Key.Space || + (e.Source is Visual source && source.FindAncestorOfType(includeSelf: true) != null)) + { + return; + } + + var selected = dataGrid.SelectedItems?.OfType().ToList(); + if (selected == null || selected.Count == 0) + { + return; + } + + var newValue = !selected.All(item => item.IsChecked); + foreach (var item in selected) + { + item.IsChecked = newValue; + } + + e.Handled = true; + }, Avalonia.Interactivity.RoutingStrategies.Tunnel); var buttonSelectAll = UiUtil.MakeButton(Se.Language.General.SelectAll, vm.SelectAllCommand); var buttonInvertSelection = UiUtil.MakeButton(Se.Language.General.InvertSelection, vm.InvertSelectionCommand); diff --git a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs index d84deeaaeb6..3fadfa3740c 100644 --- a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs +++ b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs @@ -147,31 +147,29 @@ private static Grid MakeFixesView(SplitBreakLongLinesViewModel vm) .WithMarginTop(10) .WithMarginLeft(10); - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the grid previews + // split/rebalance fixes in subtitle order. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.Fixes; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Fixes, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NumberSymbol, Binding = new Binding(nameof(SplitBreakLongLinesItem.Number)), - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - IsReadOnly = true, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(60), }, - new DataGridTemplateColumn + new SeTableViewColumn { Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((item, _) => { if (item == null) @@ -202,27 +200,27 @@ private static Grid MakeFixesView(SplitBreakLongLinesViewModel vm) }, }; }), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Auto), + // Content-sized (Auto) on the DataGrid; fits the "Split long line" / + // "Rebalance long line" pill. + Width = new GridLength(150), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Fix, Binding = new Binding(nameof(SplitBreakLongLinesItem.Fix)), - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTheme = UiUtil.DataGridNoBorderCellTheme, - IsReadOnly = true, + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, }, - }, - }; + }); dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Tools/SplitSubtitle/SplitSubtitleWindow.cs b/src/ui/Features/Tools/SplitSubtitle/SplitSubtitleWindow.cs index 7421f703f26..c1f8b5b6ffe 100644 --- a/src/ui/Features/Tools/SplitSubtitle/SplitSubtitleWindow.cs +++ b/src/ui/Features/Tools/SplitSubtitle/SplitSubtitleWindow.cs @@ -194,52 +194,49 @@ private static Border MakeOptionsView(SplitSubtitleViewModel vm) private Control MakeListView(SplitSubtitleViewModel vm) { - var dataGrid = new DataGrid + // Sorting dropped in the DataGrid -> TableView conversion: the list shows the + // split parts in output order (part 1, part 2, ...). + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.SplitItems; + dataGrid.Columns.AddRange(new TableViewColumn[] { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.SplitItems, - Columns = - { - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.NoSymbolLines, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SplitDisplayItem.Lines)), - IsReadOnly = true, + // Content-sized (Auto) on the DataGrid; TableView treats Auto as star. + Width = new GridLength(80), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.Characters, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SplitDisplayItem.Characters)), - IsReadOnly = true, + Width = new GridLength(100), }, - new DataGridTextColumn + new SeTableViewColumn { Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(SplitDisplayItem.FileName)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), + Width = new GridLength(1, GridUnitType.Star), }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSpiltItem)) { Source = vm }); + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSpiltItem)) { Source = vm }); dataGrid.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { if (e.Key is Key.Home or Key.End && dataGrid.ItemsSource is IList items && items.Count > 0) { - var target = e.Key == Key.Home ? items[0] : items[^1]; - dataGrid.SelectedItem = target; - dataGrid.ScrollIntoView(target, null); + var index = e.Key == Key.Home ? 0 : items.Count - 1; + dataGrid.SelectedIndex = index; + dataGrid.ScrollIntoView(index); e.Handled = true; } }, RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Video/BurnIn/BurnInViewModel.cs b/src/ui/Features/Video/BurnIn/BurnInViewModel.cs index 8e23df4ec99..88ac9a2fcba 100644 --- a/src/ui/Features/Video/BurnIn/BurnInViewModel.cs +++ b/src/ui/Features/Video/BurnIn/BurnInViewModel.cs @@ -106,7 +106,7 @@ public partial class BurnInViewModel : ObservableObject public Window? Window { get; set; } public bool OkPressed { get; private set; } - public DataGrid? BatchGrid { get; internal set; } + public TableView? BatchGrid { get; internal set; } public BurnInLogo BurnInLogo { get; set; } private Subtitle _subtitle = new(); @@ -520,7 +520,7 @@ private async Task InitAndStartJobItem(int index) } BatchGrid.SelectedItem = jobItem; - BatchGrid.ScrollIntoView(jobItem, null); + BatchGrid.ScrollIntoView(jobItem); }); bool result; diff --git a/src/ui/Features/Video/BurnIn/BurnInWindow.cs b/src/ui/Features/Video/BurnIn/BurnInWindow.cs index aa95561f74c..5b0425142d3 100644 --- a/src/ui/Features/Video/BurnIn/BurnInWindow.cs +++ b/src/ui/Features/Video/BurnIn/BurnInWindow.cs @@ -752,51 +752,47 @@ private static Border MakeAudioSettingsView(BurnInViewModel vm) private static Border MakeBatchView(BurnInViewModel vm) { - var dataGrid = new DataGrid + // No header sorting: the batch queue is processed top-to-bottom (jobs are + // selected/scrolled by index while generating), so the list order is meaningful. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.MinWidth = 550; // the batch column is Auto while measuring; star columns have no intrinsic width + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.JobItems; + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.InputVideoFileNameShort)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.JobItems, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.InputVideoFileNameShort)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.Resolution)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.SubtitleFile, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.SubtitleFileNameShort)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Status, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.Status)), - IsReadOnly = true, - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedJobItem)) { Source = vm }); + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.Resolution)), + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.SubtitleFile, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.SubtitleFileNameShort)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Status, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.Status)), + Width = new GridLength(110), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedJobItem)) { Source = vm }); vm.BatchGrid = dataGrid; var buttonAdd = UiUtil.MakeButton(Se.Language.General.AddDotDotDot, vm.AddCommand); diff --git a/src/ui/Features/Video/CutVideo/CutVideoViewModel.cs b/src/ui/Features/Video/CutVideo/CutVideoViewModel.cs index 410ae53b8b8..3f68f112604 100644 --- a/src/ui/Features/Video/CutVideo/CutVideoViewModel.cs +++ b/src/ui/Features/Video/CutVideo/CutVideoViewModel.cs @@ -64,7 +64,7 @@ public partial class CutVideoViewModel : ObservableObject public bool OkPressed { get; private set; } public VideoPlayerControl VideoPlayer { get; internal set; } public AudioVisualizer AudioVisualizer { get; internal set; } - public DataGrid SegmentGrid { get; internal set; } + public TableView SegmentGrid { get; internal set; } private Subtitle _subtitle = new(); private readonly StringBuilder _log; @@ -122,7 +122,7 @@ public CutVideoViewModel(IFolderHelper folderHelper, IFileHelper fileHelper, IWi JobItems = new ObservableCollection(); VideoPlayer = new VideoPlayerControl(new EmptyVideoPlayer()); AudioVisualizer = new AudioVisualizer(); - SegmentGrid = new DataGrid(); + SegmentGrid = new TableView(); Segments = new ObservableCollection(); VideoFileName = string.Empty; VideoFileSize = string.Empty; @@ -668,7 +668,7 @@ private void ImportCurrent() [RelayCommand] private void Delete() { - var selectedSegments = SegmentGrid.SelectedItems.Cast().ToList(); + var selectedSegments = SegmentGrid.SelectedItems?.Cast().ToList() ?? new List(); if (selectedSegments.Count == 0) { return; @@ -830,7 +830,9 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs keyEventArgs) return; } - if (SegmentGrid.IsFocused) + // Focus sits on the TableView row container (the DataGrid took focus itself), + // so check for focus anywhere inside the grid. + if (SegmentGrid.IsKeyboardFocusWithin) { if (keyEventArgs.Key == Key.Home && keyEventArgs.KeyModifiers == KeyModifiers.None && Segments.Count > 0) { @@ -980,7 +982,10 @@ private void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { SegmentGrid.SelectedIndex = index; - SegmentGrid.ScrollIntoView(SegmentGrid.SelectedItem, null); + if (SegmentGrid.SelectedItem is { } selectedItem) + { + SegmentGrid.ScrollIntoView(selectedItem); + } UpdateSelection(); }, DispatcherPriority.Background); } @@ -993,8 +998,8 @@ internal void SegmentsGridChanged(object? sender, SelectionChangedEventArgs e) private void UpdateSelection() { IsDeleteEnabled = SelectedSegment != null; - IsSetStartEnabled = SegmentGrid.SelectedItems.Count == 1; - IsSetEndEnabled = SegmentGrid.SelectedItems.Count == 1; + IsSetStartEnabled = SegmentGrid.SelectedItems?.Count == 1; + IsSetEndEnabled = SegmentGrid.SelectedItems?.Count == 1; } internal void SegmentsGridDoubleTapped(object? sender, TappedEventArgs e) diff --git a/src/ui/Features/Video/CutVideo/CutVideoWindow.cs b/src/ui/Features/Video/CutVideo/CutVideoWindow.cs index 8542ece468f..71e8de0d533 100644 --- a/src/ui/Features/Video/CutVideo/CutVideoWindow.cs +++ b/src/ui/Features/Video/CutVideo/CutVideoWindow.cs @@ -106,51 +106,47 @@ private static Border MakeSegmentsView(CutVideoViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + + // No header sorting: the segment list is kept in time order (segments are + // inserted in position and feed the cut output), so reordering it would be wrong. + var dataGridSubtitle = TableViewExtras.MakeTableView(); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Segments; + dataGridSubtitle.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Extended, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Segments, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Hide, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - }, - }; - dataGridSubtitle.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedSegment)) { Source = vm }); + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Number)), + Width = new GridLength(60), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.StartTime)) { Converter = fullTimeConverter }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Hide, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.EndTime)) { Converter = fullTimeConverter }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SubtitleLineViewModel.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(1, GridUnitType.Star), + }); + dataGridSubtitle.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedSegment)) { Source = vm }); dataGridSubtitle.SelectionChanged += vm.SegmentsGridChanged; dataGridSubtitle.DoubleTapped += vm.SegmentsGridDoubleTapped; vm.SegmentGrid = dataGridSubtitle; diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewWindow.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewWindow.cs index 4a3a14f8555..721f7fa8a74 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewWindow.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewWindow.cs @@ -71,87 +71,82 @@ private static Border MakeSubtitleView(EmbedTrackPreviewViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridSubtitle = new DataGrid + + // No header sorting: this is a read-only preview of the track's cues in + // subtitle order. + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + dataGridSubtitle.Width = double.NaN; + dataGridSubtitle.Height = double.NaN; + dataGridSubtitle.DataContext = vm; + dataGridSubtitle.ItemsSource = vm.Rows; + dataGridSubtitle.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Rows, - Columns = + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Number)), + Width = new GridLength(60), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGridSubtitle.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.TextOrImage, + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((item, _) => { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn + var stackPanel = new StackPanel { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn + Orientation = Orientation.Vertical, + Spacing = 5, + HorizontalAlignment = HorizontalAlignment.Left, + }; + + // Add text if available + if (!string.IsNullOrEmpty(item.Text)) { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(MatroskaSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTemplateColumn + var textBlock = new TextBlock + { + Text = item.Text, + TextWrapping = Avalonia.Media.TextWrapping.Wrap, + HorizontalAlignment = HorizontalAlignment.Left, + MaxWidth = 300 // Adjust as needed + }; + stackPanel.Children.Add(textBlock); + } + + // Add image if available + if (item.Image != null) { - Header = Se.Language.General.TextOrImage, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - CellTemplate = new FuncDataTemplate((item, _) => + var image = new Image { - var stackPanel = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 5, - HorizontalAlignment = HorizontalAlignment.Left, - }; - - // Add text if available - if (!string.IsNullOrEmpty(item.Text)) - { - var textBlock = new TextBlock - { - Text = item.Text, - TextWrapping = Avalonia.Media.TextWrapping.Wrap, - HorizontalAlignment = HorizontalAlignment.Left, - MaxWidth = 300 // Adjust as needed - }; - stackPanel.Children.Add(textBlock); - } - - // Add image if available - if (item.Image != null) - { - var image = new Image - { - Source = item.Image.Source, - MaxHeight = 100, // Adjust as needed - MaxWidth = 200, // Adjust as needed - Stretch = Avalonia.Media.Stretch.Uniform - }; - stackPanel.Children.Add(image); - } - - return stackPanel; - }) - }, - }, - }; + Source = item.Image.Source, + MaxHeight = 100, // Adjust as needed + MaxWidth = 200, // Adjust as needed + Stretch = Avalonia.Media.Stretch.Uniform + }; + stackPanel.Children.Add(image); + } + + return stackPanel; + }) + }); return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); } diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs index 6a2ef1035e1..055debc4c3e 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs @@ -47,7 +47,7 @@ public partial class EmbeddedSubtitlesEditMp4ViewModel : ObservableObject public Window? Window { get; set; } public bool OkPressed { get; private set; } - public DataGrid TracksGrid { get; internal set; } + public TableView TracksGrid { get; internal set; } private readonly StringBuilder _log; private long _startTicks; @@ -83,7 +83,7 @@ public EmbeddedSubtitlesEditMp4ViewModel(IFolderHelper folderHelper, IFileHelper Tracks = new ObservableCollection(); VideoFileName = string.Empty; ProgressText = string.Empty; - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); _log = new StringBuilder(); _timerGenerate = new Timer { Interval = 100 }; @@ -714,7 +714,10 @@ private void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { TracksGrid.SelectedIndex = index; - TracksGrid.ScrollIntoView(TracksGrid.SelectedItem, null); + if (TracksGrid.SelectedItem is { } selectedItem) + { + TracksGrid.ScrollIntoView(selectedItem); + } }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4Window.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4Window.cs index a24a37a2718..8e06f3bcece 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4Window.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4Window.cs @@ -92,84 +92,82 @@ private static Border MakeTracksView(EmbeddedSubtitlesEditMp4ViewModel vm) { var booleanToCheckMarkConverter = new BooleanToCheckMarkConverter(); var booleanToDeleteMarkConverter = new BooleanToDeleteMarkConverter(); - var dataGridTracks = new DataGrid + // No header sorting: the track list's order is the output track order + // (FfmpegGenerator.AlterEmbeddedTracksMp4 consumes Tracks in list order). + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = vm; + dataGridTracks.ItemsSource = vm.Tracks; + dataGridTracks.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = string.Empty, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Deleted)) { Mode = BindingMode.OneWay, Converter = booleanToDeleteMarkConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - // Visual cue distinguishing newly-added tracks from streams already - // present in the MP4. Without this users can't tell which row is - // theirs vs the original after clicking Add. - Header = Se.Language.General.New, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.New)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Name)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.LanguageOrTitle)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Default, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Default)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Forced, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Forced)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Codec, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Format)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.FileName)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGridTracks.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Tracks)) { Source = vm }); - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack)) { Source = vm }); + Header = string.Empty, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Deleted)) { Mode = BindingMode.OneWay, Converter = booleanToDeleteMarkConverter }, + Width = new GridLength(40), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + // Visual cue distinguishing newly-added tracks from streams already + // present in the MP4. Without this users can't tell which row is + // theirs vs the original after clicking Add. + Header = Se.Language.General.New, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.New)) { Mode = BindingMode.OneWay, Converter = booleanToCheckMarkConverter }, + Width = new GridLength(60), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Name)) { Mode = BindingMode.OneWay }, + Width = new GridLength(160), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.LanguageOrTitle)) { Mode = BindingMode.OneWay }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Default, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Default)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(80), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Forced, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Forced)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(80), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Codec, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Format)) { Mode = BindingMode.OneWay }, + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.FileName)) { Mode = BindingMode.OneWay }, + Width = new GridLength(1, GridUnitType.Star), + }); + dataGridTracks.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Tracks)) { Source = vm }); + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack)) { Source = vm }); dataGridTracks.KeyDown += (s, e) => vm.OnTracksGridKeyDown(e); dataGridTracks.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { @@ -177,7 +175,11 @@ private static Border MakeTracksView(EmbeddedSubtitlesEditMp4ViewModel vm) { var target = e.Key == Key.Home ? items[0] : items[^1]; dataGridTracks.SelectedItem = target; - dataGridTracks.ScrollIntoView(target, null); + if (target != null) + { + dataGridTracks.ScrollIntoView(target); + } + e.Handled = true; } }, Avalonia.Interactivity.RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditViewModel.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditViewModel.cs index 35132516526..53c4e35cb2e 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditViewModel.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditViewModel.cs @@ -39,7 +39,7 @@ public partial class EmbeddedSubtitlesEditViewModel : ObservableObject public Window? Window { get; set; } public bool OkPressed { get; private set; } - public DataGrid TracksGrid { get; internal set; } + public TableView TracksGrid { get; internal set; } private Subtitle _subtitle = new(); private readonly StringBuilder _log; @@ -70,7 +70,7 @@ public EmbeddedSubtitlesEditViewModel(IFolderHelper folderHelper, IFileHelper fi Tracks = new ObservableCollection(); VideoFileName = string.Empty; ProgressText = string.Empty; - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); _log = new StringBuilder(); _timerGenerate = new(); @@ -718,7 +718,10 @@ private void SelectAndScrollToRow(int index) Dispatcher.UIThread.Post(() => { TracksGrid.SelectedIndex = index; - TracksGrid.ScrollIntoView(TracksGrid.SelectedItem, null); + if (TracksGrid.SelectedItem is { } selectedItem) + { + TracksGrid.ScrollIntoView(selectedItem); + } }, DispatcherPriority.Background); } diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditWindow.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditWindow.cs index 9e4762b9afb..f6ed87d9082 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditWindow.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditWindow.cs @@ -98,74 +98,71 @@ private static Border MakeTracksView(EmbeddedSubtitlesEditViewModel vm) { var booleanToCheckMarkConverter = new BooleanToCheckMarkConverter(); var booleanToDeleteMarkConverter = new BooleanToDeleteMarkConverter(); - var dataGridTracks = new DataGrid + // No header sorting: the track list's order is the output track order + // (FfmpegGenerator.AlterEmbeddedTracksMatroska consumes Tracks in list order). + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = vm; + dataGridTracks.ItemsSource = vm.Tracks; + dataGridTracks.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = string.Empty, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Deleted)) { Mode = BindingMode.OneWay, Converter = booleanToDeleteMarkConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Name)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.LanguageOrTitle)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Default, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Default)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Forced, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Forced)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Codec, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.Format)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(EmbeddedTrack.FileName)) { Mode = BindingMode.OneWay }, - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; - dataGridTracks.Bind(DataGrid.ItemsSourceProperty, new Binding(nameof(vm.Tracks)) { Source = vm }); - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedTrck)) { Source = vm }); + Header = string.Empty, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Deleted)) { Mode = BindingMode.OneWay, Converter = booleanToDeleteMarkConverter }, + Width = new GridLength(40), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Name)) { Mode = BindingMode.OneWay }, + Width = new GridLength(160), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.LanguageOrTitle)) { Mode = BindingMode.OneWay }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Default, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Default)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(80), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Forced, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Forced)) { Converter = booleanToCheckMarkConverter, Mode = BindingMode.OneWay }, + Width = new GridLength(80), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Codec, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.Format)) { Mode = BindingMode.OneWay }, + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGridTracks.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(EmbeddedTrack.FileName)) { Mode = BindingMode.OneWay }, + Width = new GridLength(1, GridUnitType.Star), + }); + dataGridTracks.Bind(TableView.ItemsSourceProperty, new Binding(nameof(vm.Tracks)) { Source = vm }); + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedTrck)) { Source = vm }); dataGridTracks.KeyDown += (s, e) => vm.OnTracksGridKeyDown(e); dataGridTracks.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => { @@ -173,7 +170,11 @@ private static Border MakeTracksView(EmbeddedSubtitlesEditViewModel vm) { var target = e.Key == Key.Home ? items[0] : items[^1]; dataGridTracks.SelectedItem = target; - dataGridTracks.ScrollIntoView(target, null); + if (target != null) + { + dataGridTracks.ScrollIntoView(target); + } + e.Handled = true; } }, Avalonia.Interactivity.RoutingStrategies.Tunnel); diff --git a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs index b4f4b62877b..3a48eccaded 100644 --- a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs +++ b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs @@ -28,7 +28,7 @@ public partial class PickOnlineSubtitleViewModel : ObservableObject [ObservableProperty] private bool _isOkEnabled; public Window? Window { get; set; } - public DataGrid TracksGrid { get; set; } + public TableView TracksGrid { get; set; } public bool OkPressed { get; private set; } public string? SelectedSubtitlePath { get; private set; } @@ -44,7 +44,7 @@ public PickOnlineSubtitleViewModel(IFileHelper fileHelper, IWindowService window Tracks = new ObservableCollection(); PreviewRows = new ObservableCollection(); StatusText = string.Empty; - TracksGrid = new DataGrid(); + TracksGrid = new TableView(); } /// @@ -83,7 +83,7 @@ public void Initialize(IReadOnlyList subtitles, string? var initial = Tracks.First(); SelectedTrack = initial; TracksGrid.SelectedItem = initial; - TracksGrid.ScrollIntoView(initial, null); + TracksGrid.ScrollIntoView(initial); } partial void OnSelectedTrackChanged(OnlineSubtitleTrackDisplay? value) diff --git a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleWindow.cs b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleWindow.cs index 6871f528032..a8281025c67 100644 --- a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleWindow.cs +++ b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleWindow.cs @@ -71,50 +71,53 @@ public PickOnlineSubtitleWindow(PickOnlineSubtitleViewModel vm) private static Border MakeTracksView(PickOnlineSubtitleViewModel vm) { - var dataGridTracks = new DataGrid + var dataGridTracks = TableViewExtras.MakeTableView(multiSelect: false); + dataGridTracks.Width = double.NaN; + dataGridTracks.Height = double.NaN; + dataGridTracks.DataContext = vm; + dataGridTracks.ItemsSource = vm.Tracks; + + var columnLanguage = new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.Tracks, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.Language, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Language)), - IsReadOnly = true, - Width = new DataGridLength(1.6, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Name)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - new DataGridTextColumn - { - Header = Se.Language.General.Format, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Format)), - IsReadOnly = true, - Width = new DataGridLength(0.6, DataGridLengthUnitType.Star), - }, - }, + Header = Se.Language.General.Language, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Language)), + Width = new GridLength(1.6, GridUnitType.Star), + }; + var columnName = new SeTableViewColumn + { + Header = Se.Language.General.Name, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Name)), + Width = new GridLength(1, GridUnitType.Star), + }; + var columnFormat = new SeTableViewColumn + { + Header = Se.Language.General.Format, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleTrackDisplay.Format)), + Width = new GridLength(0.6, GridUnitType.Star), }; - dataGridTracks.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack)) { Mode = BindingMode.TwoWay }); - dataGridTracks.DoubleTapped += (_, _) => + dataGridTracks.Columns.Add(columnLanguage); + dataGridTracks.Columns.Add(columnName); + dataGridTracks.Columns.Add(columnFormat); + + // Header sorting is safe here: the list is a pick list of search results whose + // order is presentation-only - the chosen subtitle is consumed via SelectedTrack, + // never via the collection's order or indexes. + var sorter = new TableViewHeaderSorter(dataGridTracks); + sorter.AddSortable(columnLanguage, x => x.Language) + .AddSortable(columnName, x => x.Name) + .AddSortable(columnFormat, x => x.Format); + + dataGridTracks.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedTrack)) { Mode = BindingMode.TwoWay }); + dataGridTracks.DoubleTapped += (_, e) => { - if (vm.IsOkEnabled) + // A double click on a column header (sorting) must not count as "pick". + if (vm.IsOkEnabled && !TableViewExtras.IsInColumnHeader(e.Source as Avalonia.Visual)) { vm.OkCommand.Execute(null); } @@ -128,51 +131,46 @@ private static Border MakePreviewView(PickOnlineSubtitleViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); - var dataGridPreview = new DataGrid + + // No header sorting: this is a read-only preview of the subtitle's cues in + // subtitle order. + var dataGridPreview = TableViewExtras.MakeTableView(multiSelect: false); + dataGridPreview.Width = double.NaN; + dataGridPreview.Height = double.NaN; + dataGridPreview.DataContext = vm; + dataGridPreview.ItemsSource = vm.PreviewRows; + dataGridPreview.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.PreviewRows, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.NumberSymbol, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Number)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Show, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Duration, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Text, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Text)), - IsReadOnly = true, - Width = new DataGridLength(1, DataGridLengthUnitType.Star), - }, - }, - }; + Header = Se.Language.General.NumberSymbol, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Number)), + Width = new GridLength(60), // was content-sized (Auto) on the DataGrid + }); + dataGridPreview.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Show, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Show)) { Converter = fullTimeConverter }, + Width = new GridLength(120), // was content-sized (Auto) on the DataGrid + }); + dataGridPreview.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Duration, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Duration)) { Converter = shortTimeConverter }, + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGridPreview.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Text, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(OnlineSubtitleCueDisplay.Text)), + Width = new GridLength(1, GridUnitType.Star), + }); return UiUtil.MakeBorderForControlNoPadding(dataGridPreview); } diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs index 83978823c49..18c3a085c0e 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs @@ -138,7 +138,7 @@ public partial class SpeechToTextViewModel : ObservableObject public TextBox TextBoxConsoleLogBatch { get; internal set; } public TextBox TextBoxConsoleLogSingle { get; internal set; } public Button? CopyConsoleLogButton { get; internal set; } - public DataGrid BatchGrid { get; internal set; } + public TableView BatchGrid { get; internal set; } private bool _unknownArgument; private bool _cudaOutOfMemory; @@ -290,7 +290,7 @@ public SpeechToTextViewModel(IWindowService windowService, IFileHelper fileHelpe TranscribedSubtitle = new Subtitle(); TextBoxConsoleLogBatch = new TextBox(); TextBoxConsoleLogSingle = new TextBox(); - BatchGrid = new DataGrid(); + BatchGrid = new TableView(); ReDownloadText = string.Empty; EngineDownloadHint = string.Empty; _audioTrackNumber = -1; @@ -1749,7 +1749,7 @@ private void StartNext(Subtitle? transcribedSubtitle) } BatchGrid.SelectedItem = jobItem; - BatchGrid.ScrollIntoView(jobItem, null); + BatchGrid.ScrollIntoView(jobItem); }); var startGenerateAudioFileOk = GenerateAudioFile(_videoFileName, _audioTrackNumber); @@ -3443,7 +3443,7 @@ private async Task Transcribe() } BatchGrid.SelectedItem = jobItem; - BatchGrid.ScrollIntoView(jobItem, null); + BatchGrid.ScrollIntoView(jobItem); }); } diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs index da43e6239b1..b4d8787dfec 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs @@ -522,44 +522,38 @@ private static Grid MakeConsoleLogAndBatchView(SpeechToTextViewModel vm) vm.TextBoxConsoleLogBatch = textBoxConsoleLog; - var dataGrid = new DataGrid + // No header sorting: the batch queue is processed top-to-bottom (the run aliases + // BatchItems and walks it in list order), so the list order is meaningful. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.BatchItems; + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.FileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SpeechToTextJobItem.InputVideoFileNameShort)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.BatchItems, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.FileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SpeechToTextJobItem.InputVideoFileNameShort)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SpeechToTextJobItem.SizeDisplay)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Status, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(SpeechToTextJobItem.Status)), - IsReadOnly = true, - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedBatchItem)) { Source = vm }); + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SpeechToTextJobItem.SizeDisplay)), + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Status, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(SpeechToTextJobItem.Status)), + Width = new GridLength(110), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedBatchItem)) { Source = vm }); AutomationProperties.SetName(dataGrid, Se.Language.General.BatchMode); vm.BatchGrid = dataGrid; @@ -594,7 +588,7 @@ private static Grid MakeConsoleLogAndBatchView(SpeechToTextViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, }; - // hack to make drag and drop work on the DataGrid - also on empty rows + // hack to make drag and drop work on the grid - also on empty rows var dropHost = new Border { Background = Brushes.Transparent, diff --git a/src/ui/Features/Video/TransparentSubtitles/TransparentSubtitlesWindow.cs b/src/ui/Features/Video/TransparentSubtitles/TransparentSubtitlesWindow.cs index 45d6ea6b671..8a0b3e1259b 100644 --- a/src/ui/Features/Video/TransparentSubtitles/TransparentSubtitlesWindow.cs +++ b/src/ui/Features/Video/TransparentSubtitles/TransparentSubtitlesWindow.cs @@ -537,51 +537,47 @@ private static Border MakePreviewView(TransparentSubtitlesViewModel vm) private static Border MakeBatchView(TransparentSubtitlesViewModel vm) { - var dataGrid = new DataGrid + // No header sorting: the batch queue is processed top-to-bottom, so the list + // order is meaningful. + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + dataGrid.Width = double.NaN; + dataGrid.Height = double.NaN; + dataGrid.MinWidth = 550; // the batch column is Auto while measuring; star columns have no intrinsic width + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.JobItems; + dataGrid.Columns.Add(new SeTableViewColumn { - AutoGenerateColumns = false, - SelectionMode = DataGridSelectionMode.Single, - CanUserResizeColumns = true, - CanUserSortColumns = true, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Width = double.NaN, - Height = double.NaN, - DataContext = vm, - ItemsSource = vm.JobItems, - Columns = - { - new DataGridTextColumn - { - Header = Se.Language.General.SubtitleFileName, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.SubtitleFileNameShort)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Size, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.Resolution)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.VideoFile, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.InputVideoFileNameShort)), - IsReadOnly = true, - }, - new DataGridTextColumn - { - Header = Se.Language.General.Status, - CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme, - Binding = new Binding(nameof(BurnInJobItem.Status)), - IsReadOnly = true, - }, - }, - }; - dataGrid.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedJobItem)) { Source = vm }); + Header = Se.Language.General.SubtitleFileName, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.SubtitleFileNameShort)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Size, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.Resolution)), + Width = new GridLength(90), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.VideoFile, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.InputVideoFileNameShort)), + Width = new GridLength(1, GridUnitType.Star), + }); + dataGrid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Status, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(nameof(BurnInJobItem.Status)), + Width = new GridLength(110), // was content-sized (Auto) on the DataGrid + }); + dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedJobItem)) { Source = vm }); var buttonAdd = UiUtil.MakeButton(Se.Language.General.AddDotDotDot, vm.AddCommand); var buttonRemove = UiUtil.MakeButton(Se.Language.General.Remove, vm.RemoveCommand); diff --git a/src/ui/Logic/TableViewExtras.cs b/src/ui/Logic/TableViewExtras.cs index 85a8177c90e..f66cb720134 100644 --- a/src/ui/Logic/TableViewExtras.cs +++ b/src/ui/Logic/TableViewExtras.cs @@ -296,6 +296,37 @@ static void Apply(Control container, int index, IBrush alternatingBrush) tableView.ContainerIndexChanged += (_, e) => Apply(e.Container, e.NewIndex, brush); } + /// + /// Home/End jump to the first/last row even when the TableView itself (not a row) + /// has keyboard focus - ListBox's native handling only runs with focus on an item. + /// Skipped when the key originates in a TextBox (e.g. an in-cell editor). + /// + public static void AttachHomeEndNavigation(TableView tableView) + { + tableView.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => + { + if (e.Key is not (Key.Home or Key.End) || e.Source is TextBox) + { + return; + } + + if (tableView.ItemsSource is not System.Collections.IList items || items.Count == 0) + { + return; + } + + var target = e.Key == Key.Home ? items[0] : items[^1]; + if (target == null) + { + return; + } + + tableView.SelectedItem = target; + tableView.ScrollIntoView(target); + e.Handled = true; + }, Avalonia.Interactivity.RoutingStrategies.Tunnel); + } + /// /// Space toggles the checkbox value of every selected row - all rows checked means /// uncheck all, otherwise check all. This is the piece of the DataGrid-era