mirror of
https://github.com/microsoft/microsoft-ui-xaml.git
synced 2026-10-28 16:35:19 +08:00
Merges the TableView feature branch `user/hik/feature/TableView` into `main`. Each change below was reviewed and merged into the feature branch as its own PR. This PR brings them to `main` together. ## Included PRs - **#12267 Hierarchical and live data operations (review fixes)** (@dipeshmsft) - Group/tree collapse keeps selection consistent. - Live-shaping subscriptions are hardened. - Code-review and benchmark-app fixes. - **#12256 [2/3] TableView: cell UIA names, stale peers, grow-only Auto width, regroup selection, HC backplates, rounded focus** - Cell accessible names don't repeat the column header. - Stale GridItem peers throw ElementNotAvailable. - Group StructureChanged is gated on ListenerExists. - Auto columns only grow within a data set. - A regroup keeps the moved selected item selected. - No high-contrast backplates on hovered/pressed rows. - Keyboard focus rects are rounded on cells, header cells and group headers. - **#12248 [3/3] TableView: ListView paging and far-row focus, header naming/focus/gripper, source ownership, safe sort, edit rollback** - **Paging and focus:** ListView-style PageUp/PageDown (group headers are valid targets); far-row focus on Home/End and Ctrl+Home/End. - **Headers:** naming, focus restore and resize-gripper fixes. - **Source ownership:** single-owner `TableViewSource` with release/reclaim on Unloaded/Loaded. - **Sorting:** fails safely over null or duplicate rows. - **Editing:** rollback of invalid values on forced edit teardown. - **Sample-app findings fixed:** a layout-cycle crash, fast-paging focus, ComboBox editor focus, Ctrl+Space selection. - **#12120 TableView: add usage telemetry and Test in Production** (@Saharsh979) - Reliability (TiP) and usage telemetry on the existing Tabular providers. No public API change. - Two TiP tests: `TableViewInitializationTest` runs on `OnApplyTemplate`; `TableViewOperationTest` covers source swap, sort, filter, grouping, selection and edit begin/commit/cancel. - Usage telemetry events: `TableView_SessionSummary` and `TableView_Error`. - Build: `TableView.TestInProduction.props` is imported by Tabular only, and `MuxcTraceLogging.h` gains `KEYWORD_TABLEVIEW`. `main` has 3 NavigationView commits that aren't on the feature branch. They don't touch TableView, and the two sides merge with no conflicts. ## Validation On a test VM, using the feature-branch bits before the final #12248 rebase (before #12120 landed): - **TableView API suite:** 581/582 passed. - **TableView interaction suite:** 122/122 passed. - **TableViewSampleApp:** sanity pass on every page (paging, grouping, editing, sort, navigating away and back). - **Build fix (32f8ff44a):** #12120 added a TiP generation argument to `SyncTableViewSourceSort`, but the `RerankCustomSortAfterReclaim` call from #12248 still passed two arguments. Git merged the two cleanly, so the conflict only showed up as a C2660 build error. The rerank is driven by the control, not the user, so it now passes `0` (no TiP operation). ## Known issues - **Unnamed visual cells (8504536eb):** a cell that shows an unnamed visual, such as a ProgressBar, is now named after its column instead of "blank", so `VerifyUnnamedVisualCellIsNotNamedBlank` passes (3/3 on the test VM). - **3 localization tests are temporarily disabled** (in #12248). - **Follow-ups from the #12248 review:** - PageUp/PageDown distance with variable-height rows. - The identity-validation cache misses changes in collections that don't raise change notifications. - Interaction coverage for header hover/press visuals. - **Theme resources:** unused `TabularSurface*` theme keys and their `MUX_TabularSurface*` aliases were removed. They only shipped in experimental packages, so this is worth a release note. --------- Co-authored-by: Dipesh Kumar <dipeshkumar@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dipesh Kumar <85861525+dipeshmsft@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hiteshkrmsft <169995515+hiteshkrmsft@users.noreply.github.com> Co-authored-by: Saharsh Ananta Jaiswal <sahjaiswal@microsoft.com> Copilot-Session: 1a711a5a-8e3c-4114-a58b-68a5e3784da9 Copilot-Session: 1b4b389b-9df6-4996-bd1c-b4a30a5da768 Copilot-Session: d8e58903-f3be-45f2-8124-5cdcfcca0428 Copilot-Session: b54b3a5d-2e82-43fa-9664-760e896cc4a3 Copilot-Session: 1cda586e-f933-4841-9256-463216c2c118 Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 Copilot-Session: a4f445d6-e2e7-416c-a777-7bc28103da7e
211 lines
7.3 KiB
C++
211 lines
7.3 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
|
|
|
#pragma once
|
|
|
|
#include "TableViewColumnHeaderAutomationPeer.h"
|
|
|
|
// Keep the existing Grid layout/input behavior, but attach the header peer to the
|
|
// actual focus and hit-test target. This implementation-only type needs no WinRT API.
|
|
class TableViewHeaderCell :
|
|
public ReferenceTracker<TableViewHeaderCell, winrt::Microsoft::UI::Xaml::Controls::GridT, winrt::composable>
|
|
{
|
|
public:
|
|
TableViewHeaderCell(winrt::TableView const& table, winrt::TableViewColumn const& column)
|
|
: m_table(winrt::make_weak(table)), m_column(winrt::make_weak(column))
|
|
{
|
|
// A Grid has no pointer overrides. Never marks the events handled, so Tapped-to-sort and
|
|
// focus are unaffected.
|
|
PointerEntered({ this, &TableViewHeaderCell::OnPointerEntered });
|
|
PointerExited({ this, &TableViewHeaderCell::OnPointerExited });
|
|
PointerPressed({ this, &TableViewHeaderCell::OnPointerPressed });
|
|
PointerReleased({ this, &TableViewHeaderCell::OnPointerReleased });
|
|
PointerCaptureLost({ this, &TableViewHeaderCell::OnPointerLost });
|
|
PointerCanceled({ this, &TableViewHeaderCell::OnPointerLost });
|
|
// Theme brushes are resolved per state, so a theme change while hovered must re-resolve them.
|
|
ActualThemeChanged({ this, &TableViewHeaderCell::OnActualThemeChanged });
|
|
}
|
|
|
|
winrt::hstring GetRuntimeClassName() const
|
|
{
|
|
return winrt::hstring_name_of<winrt::Grid>();
|
|
}
|
|
|
|
winrt::AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
return winrt::make<TableViewColumnHeaderAutomationPeer>(
|
|
get_strong().as<winrt::FrameworkElement>(), m_table.get(), m_column.get());
|
|
}
|
|
|
|
private:
|
|
enum class PointerVisualState
|
|
{
|
|
Normal,
|
|
PointerOver,
|
|
Pressed,
|
|
};
|
|
|
|
void OnPointerEntered(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs&)
|
|
{
|
|
m_isPointerOver = true;
|
|
UpdatePointerVisualState();
|
|
}
|
|
|
|
void OnPointerExited(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs&)
|
|
{
|
|
m_isPointerOver = false;
|
|
m_isPressed = false;
|
|
UpdatePointerVisualState();
|
|
}
|
|
|
|
void OnPointerPressed(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs&)
|
|
{
|
|
m_isPressed = true;
|
|
UpdatePointerVisualState();
|
|
}
|
|
|
|
void OnPointerReleased(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs&)
|
|
{
|
|
m_isPressed = false;
|
|
UpdatePointerVisualState();
|
|
}
|
|
|
|
void OnPointerLost(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args)
|
|
{
|
|
// The resize gripper captures the pointer for its drag; that capture ending bubbles here
|
|
// while the pointer is still over the header, so it must not reset the hover state.
|
|
if (IsInsideResizeGripper(args.OriginalSource()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_isPointerOver = false;
|
|
m_isPressed = false;
|
|
UpdatePointerVisualState();
|
|
}
|
|
|
|
void OnActualThemeChanged(const winrt::FrameworkElement&, const winrt::IInspectable&)
|
|
{
|
|
UpdatePointerVisualState(true /* force */);
|
|
}
|
|
|
|
bool IsInsideResizeGripper(const winrt::IInspectable& source)
|
|
{
|
|
winrt::Panel const self = *this;
|
|
for (auto current = source.try_as<winrt::DependencyObject>();
|
|
current && current != self;
|
|
current = winrt::VisualTreeHelper::GetParent(current))
|
|
{
|
|
if (current.try_as<winrt::ResizeGripper>())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsSortable() const
|
|
{
|
|
auto const table = m_table.get();
|
|
auto const column = m_column.get();
|
|
return table && column && table.CanUserSortColumns() && column.CanSort();
|
|
}
|
|
|
|
void UpdatePointerVisualState(bool force = false)
|
|
{
|
|
auto state = PointerVisualState::Normal;
|
|
if (IsSortable())
|
|
{
|
|
state = m_isPressed ? PointerVisualState::Pressed
|
|
: m_isPointerOver ? PointerVisualState::PointerOver
|
|
: PointerVisualState::Normal;
|
|
}
|
|
|
|
if (state == m_visualState && !force)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_visualState = state;
|
|
|
|
winrt::ContentPresenter content{ nullptr };
|
|
if (Children().Size() > 0)
|
|
{
|
|
content = Children().GetAt(0).try_as<winrt::ContentPresenter>();
|
|
}
|
|
|
|
auto const indicator = TableView::FindSortIndicator(*this);
|
|
|
|
auto const table = m_table.get();
|
|
if (state == PointerVisualState::Normal || !table)
|
|
{
|
|
Background(winrt::SolidColorBrush{ winrt::Colors::Transparent() });
|
|
ApplyStateForeground(content, indicator, nullptr);
|
|
return;
|
|
}
|
|
|
|
auto const tableImpl = winrt::get_self<TableView>(table);
|
|
const bool pressed = state == PointerVisualState::Pressed;
|
|
// A null brush would make the cell stop taking pointer input.
|
|
winrt::Brush background = tableImpl->LookupHeaderStateBrush(pressed
|
|
? L"TabularSurfaceHeaderBackgroundPressedBrush"
|
|
: L"TabularSurfaceHeaderBackgroundPointerOverBrush");
|
|
if (!background)
|
|
{
|
|
background = winrt::SolidColorBrush{ winrt::Colors::Transparent() };
|
|
}
|
|
Background(background);
|
|
|
|
winrt::Brush foreground{ nullptr };
|
|
if (tableImpl->IsHighContrast())
|
|
{
|
|
foreground = tableImpl->LookupHeaderStateBrush(pressed
|
|
? L"TabularSurfaceHeaderForegroundPressedBrush"
|
|
: L"TabularSurfaceHeaderForegroundPointerOverBrush");
|
|
}
|
|
ApplyStateForeground(content, indicator, foreground);
|
|
}
|
|
|
|
// The chevron needs the same HC foreground as the text, or it vanishes on the highlight fill. The
|
|
// text backplate is turned off with it, as the row and group header templates do, so it does not
|
|
// fight the highlight fill.
|
|
static void ApplyStateForeground(
|
|
winrt::ContentPresenter const& content,
|
|
winrt::SortIndicator const& indicator,
|
|
winrt::Brush const& foreground)
|
|
{
|
|
if (content)
|
|
{
|
|
if (foreground)
|
|
{
|
|
content.Foreground(foreground);
|
|
content.HighContrastAdjustment(winrt::ElementHighContrastAdjustment::None);
|
|
}
|
|
else
|
|
{
|
|
content.ClearValue(winrt::ContentPresenter::ForegroundProperty());
|
|
content.ClearValue(winrt::UIElement::HighContrastAdjustmentProperty());
|
|
}
|
|
}
|
|
if (indicator)
|
|
{
|
|
if (foreground)
|
|
{
|
|
indicator.Foreground(foreground);
|
|
indicator.HighContrastAdjustment(winrt::ElementHighContrastAdjustment::None);
|
|
}
|
|
else
|
|
{
|
|
indicator.ClearValue(winrt::Control::ForegroundProperty());
|
|
indicator.ClearValue(winrt::UIElement::HighContrastAdjustmentProperty());
|
|
}
|
|
}
|
|
}
|
|
|
|
winrt::weak_ref<winrt::TableView> m_table;
|
|
winrt::weak_ref<winrt::TableViewColumn> m_column;
|
|
PointerVisualState m_visualState{ PointerVisualState::Normal };
|
|
bool m_isPointerOver{ false };
|
|
bool m_isPressed{ false };
|
|
};
|