mirror of
https://github.com/microsoft/microsoft-ui-xaml.git
synced 2026-10-28 16:35:19 +08:00
## Fixes | Internal issue | Type | Change | |---|---|---| | https://task.ms/64475625 | Bug | Column headers are a templated, restylable `TableViewColumnHeader` with visual states | | https://task.ms/64475623 | Bug | Header typography comes from the header Style, not C++ local values | | https://task.ms/64143451 | Bug | Header column separators are always drawn (no new property); hide them by overriding `TabularSurfaceHeaderSeparatorBrush` | | https://task.ms/63996584 | Task | Column headers expose the UIA Transform pattern so UIA clients can resize columns | | https://task.ms/64475611 | Bug | Outer border uses new `TabularSurfaceOuterBorderBrush` (`CardStrokeColorDefault`); grid-line values were in #12349 | | https://task.ms/64475629 | Bug | Default group header uses theme tokens (SemiBold key style, 8px spacing); the item count inherits the header `Foreground` | | https://task.ms/64652961 | Task | Row recycling no longer regenerates cells or refreshes row chrome twice (scroll performance) | | https://task.ms/63790855 | Task | Sort: `CanSort` is enforced and `SortByColumn(column, None)` always clears; a sort through a source another table owns returns false; no spurious `Sorted`; an app-owned sort survives a `CanSort`/`CanUserSortColumns` opt-out | | https://task.ms/63787021 | Task | Cell automation-peer constructor validates its arguments; `BeginEdit` rolls back when it throws | Not changed: https://task.ms/64512732 (by design: Auto columns follow content width), https://task.ms/64475627 (design decision: keep chevron sort glyphs), https://task.ms/64513223 (investigation only; fix deferred). ## PR Type - [x] Bugfix - [x] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build related changes - [x] Documentation content changes - [ ] Other (please describe): ## Description Follow-up to #12349 (merged) and #12285. Column headers move from code-built Grids to a new templated `TableViewColumnHeader`, restylable through `TableView.ColumnHeaderStyle` or `TableViewColumn.HeaderStyle`. Header peers gain the UIA Transform pattern for column resize. Sort behavior is tightened around `CanSort`, opt-outs and shared `TableViewSource` ownership. Row recycling reuses cells. Outer border, header lines and the default group header follow Fluent tokens. Review-driven fixes included here: - The header Style's `TargetType` `TypeName` is kept alive while it is compared; a Style with only `BasedOn` typed is applied, and its setters are read before applying it seals the Style. Incompatible, untyped or cyclic `BasedOn` Styles are ignored with a debug diagnostic. - A header disabled by its Style reports `IsEnabled=false` and is not invokable through UIA. - UIA `Transform.Resize` converts physical pixels through the table's full visual scale (not only `RasterizationScale`), and a width change below the drag jitter deadband is written once, exactly. - Disabling a header in its High Contrast hover/pressed state keeps the disabled foreground (state order fixed); the disabled sort glyph follows the text brush across theme changes. - A `CanSort` opt-out made inside `BeginningEdit` is replayed after the edit it let open closes, and rebuilds headers even when a `Sorted` handler throws. - Header rebuilds caused by `Content`/`Header` or `Columns` changes keep focus in the header band, respect an app's own focus move, and release `UIElement` headers back to the app. - A disabled group header does not dim its item count twice. The count opacity follows `ContentTemplate` changes while the header is disabled. <details><summary>Product changes by file</summary> | File | +/- | What changed | Items | |---|---|---|---| | `TableView/TableView.cpp` (+ .h) | +684 / −382 | `RebuildHeaders` creates `TableViewColumnHeader` controls; applies `ColumnHeaderStyle`/`HeaderStyle` with TargetType/BasedOn checks; density MinHeight/Padding only when the Style doesn't set them<br>`ReleaseHeaderCells` and header-focus handling for removed/replaced columns, re-entrant rebuilds and app focus moves<br>Header bottom rule thickness set on `PART_HeaderRow`; header lines no longer follow `GridLinesVisibility`<br>Row prepare/clear refresh chrome once; `TryResizeColumnFromAutomation` writes small widths exactly | 64475625, 64475623, 64143451, 64652961, 63996584, 63790855 | | `TableView/TableViewColumnHeader.cpp` (+ .h; replaces `TableViewHeaderCell.h`) | +435 / −210 | New sealed templated `ContentControl` with read-only `Column`<br>Resolves `PART_ContentPresenter`, `PART_GridLine`, `PART_SortIndicatorHost`, `PART_ResizeGripperHost`; hosts the sort glyph and gripper<br>Drives `CommonStates`/`HighContrastStates`/`SortStates`; disabled sort glyph bound to the text brush | 64475625, 64475623 | | `TableView/TableViewRow.cpp` (+ .h) | +191 / −35 | Recycled rows keep their cells unless cell automation peers exist; chrome refreshed once; tooltips re-applied on recycle<br>`BeginCellEdit` restores display element, edit state and focus on throw | 64652961, 63787021 | | `TableView/TableView.xaml` | +128 / −18 | Default `TableViewColumnHeader` Style/template: typography setters, visual states, parts<br>Outer border uses `TabularSurfaceOuterBorderBrush`; header lines use `TabularSurfaceHeaderSeparatorBrush`<br>Group header: `TableViewGroupExpanderSpacing` (8), padding 8,8,12,8, key/count text styles, count inherits `Foreground` | 64475625, 64475623, 64475611, 64475629 | | `TableView/TableViewColumnHeaderAutomationPeer.cpp` (+ .h) | +127 / −9 | `ITransformProvider`: `Resize` converts physical px through the visual scale; `CanResize` tracks gripper availability (pinned → false)<br>`IsEnabled` follows the header; name read from the content presenter | 63996584, 64475625 | | `TableView/TableView_Sort.cpp` | +113 / −39 | `SortByColumn(None)` bypasses the `CanSort` gate; other sorts refused<br>Sort through a source owned by another table returns false; implicit clears keep the app's sort and raise no `Sorted`; app-lit columns left alone on opt-out | 63790855 | | `TableView/TableView_Layout.cpp` | +74 / −14 | Realized-row walk only when a visible Auto column exists<br>Transform `CanResize` changes raised for existing header peers | 64652961, 63996584 | | `TableView/TableView_Editing.cpp` | +63 / −15 | `BeginEdit` unwinds on throw; `CanSort` opt-out during `BeginningEdit` queued until the edit closes | 63787021, 63790855 | | `TableView/TableViewCellAutomationPeer.cpp` (+ .h, `TableViewCell.h`) | +48 / −0 | Public constructor throws `E_INVALIDARG` for null args, negative/out-of-range index or a foreign column; internal path uses `TrustedOwnerTag` | 63787021 | | `TableView/TableViewGroupHeader.cpp` (+ .h) | +39 / −0 | Default count's opacity is not applied twice when disabled | 64475629 | | `TableView/TableView.idl` | +31 / −13 | `TableViewColumnHeader`, `TableView.ColumnHeaderStyle`, `TableViewColumn.HeaderStyle`<br>`CanSort`/`SortByColumn`/`ToggleSortDirection`/`GridLinesVisibility` docs; cell-peer `E_INVALIDARG`; group-header parts | 64475625, 63790855, 64143451, 63787021 | | `TableView/TableView_Columns.cpp` | +19 / −0 | Releases removed columns' header cells; old `UIElement` `Header` returned to the app on change | 64475625 | | `TableView/TableViewColumn.cpp` | +7 / −0 | `HeaderStyle` change queues a header rebuild | 64475625 | | `TableView/TableViewSource.h` | +6 / −0 | `IsOwnedByOther` helper for sort sync | 63790855 | | `CommonStyles/TabularSurfaces_themeresources.xaml` | +5 / −2 | New `TabularSurfaceOuterBorderBrush` (Dark/Light/HC); group count opacity documented as secondary-over-primary text (0.77 Dark) | 64475611, 64475629 | | `TableView/TableView.vcxitems` | +2 / −1 | Swap `TableViewHeaderCell.h` for `TableViewColumnHeader.*` | 64475625 | | Generated / metadata (`Generated/TableView.properties.*`, `Generated/TableViewColumn.properties.*`, `dll-tabular/XamlMetadataProviderGenerated.h`) | +123 / −0 | Codegen for the new properties and type | 64475625 | **Total: 17 rows, 29 product files, +2117 / −738.** Also: 19 test files (+3718 / −32), 3 docs (+161 / −35), 6 sample files (+2 / −9). </details> ### New preview API (needs API review) - **`TableViewColumnHeader`**: sealed, templated `ContentControl`, created only by `TableView`. - Read-only `Column` property. - Template parts (optional): `PART_ContentPresenter`, `PART_GridLine`, `PART_SortIndicatorHost`, `PART_ResizeGripperHost`. - Visual state groups: `CommonStates`, `HighContrastStates`, `SortStates`. - **`TableView.ColumnHeaderStyle`**: Style applied to every column header. - **`TableViewColumn.HeaderStyle`**: per-column Style; wins over `TableView.ColumnHeaderStyle`. There is no `HeaderGridLinesVisibility` property. `GridLinesVisibility` affects body lines only; header lines are always drawn. To hide them, override `TabularSurfaceHeaderSeparatorBrush` with `Transparent`. ### Current Behavior - Headers are built in C++ with no template, no visual states and forced local typography, so apps can't restyle them. Header separators don't show. - Header peers report `HeaderItem` without Transform, so UIA clients can't resize columns. - `SortByColumn` ignores `CanSort`; `CanSort=false` on a sorted column leaves the sort in place; sorting through a source another table owns reports success; some implicit clears raise `Sorted(None)` while the app's sort remains. - The cell peer constructor accepts invalid arguments; a throw in `BeginEdit` leaves edit state half-open. - Recycling a row rebuilt every cell, refreshed chrome up to twice and invalidated the table's measure. - The outer border has no dedicated brush; the default group header uses raw weights and off-grid spacing. ### New Behavior - Headers use the templated `TableViewColumnHeader` (see API above); typography, padding and brushes come from Style setters. - Header peers implement `ITransformProvider` through the existing resize path (Min/Max clamping, scale conversion; pinned columns report `CanResize=false`). - `CanSort=false` blocks header clicks, `SortByColumn` (except `None`) and `ToggleSortDirection`, and clears the column's own active sort; sorts through a source owned by another table return false; `Sorted` is raised only when the table's sort state changes. - Cell peer constructor throws `E_INVALIDARG` on bad input; `BeginEdit` rolls back on failure. - Recycled rows reuse their cells unless cell automation peers exist; chrome refreshes once. > **Visible changes** > - Outer border uses `CardStrokeColorDefault`. > - Header lines are always shown, even when `GridLinesVisibility` is `None` or `Horizontal`. > - Group header restyled: 8px spacing; the count follows the header `Foreground`. > - Disabled header text and sort glyph dim. > - TableView samples no longer opt out of High Contrast backplates app-wide. ## Customer Impact Restylable column headers, visible header separators, Fluent-aligned outer border and group headers, UIA column resize for assistive technology, smoother scrolling and predictable sort events. ## Regression Potential - [ ] Low risk — isolated change, limited scope - [x] Medium risk — touches shared components or public APIs - [ ] High risk — architectural or breaking API change Header construction (focus restore, frozen columns, UIA peer, tooltips, resize, sort glyph) moved to a templated control. Sort event semantics are tighter: `CanSort` is enforced on programmatic sorts and fewer `Sorted` events fire. Outer border color and always-on header lines change visibly. Covered by new and existing tests. ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [x] I have added tests to cover my changes - [x] Existing tests pass locally Full TableView API and interaction suites on test VMs: 877/882 pass at 224d10d5c; 3 Localization tests are [Ignore] and 2 intermittent failures passed 3/3 on rerun. Product, test apps and TableView sample apps build (x64 chk). Also covered: `VerifyDisabledGroupHeaderCountFollowsContentTemplateChanges`. Key new tests: - `VerifyColumnHeaderTemplateParts`, `VerifyColumnHeaderSortStates`, `VerifyColumnHeaderTypographyHasNoLocalValues` - `VerifyColumnHeaderStyleApplies`, `VerifyBasedOnOnlyColumnHeaderStyleApplies`, `VerifyIncompatibleColumnHeaderStyleIsIgnored` - `VerifyHeaderGridLinesShowWhateverTheBodyGridLines`, `VerifyTransparentHeaderSeparatorBrushHidesHeaderLines`, `VerifyGridLineAndOuterBorderBrushesMatchFluentStrokeTokens` - `VerifyDisablingHighContrastHoveredHeaderKeepsDisabledForeground`, `VerifyDefaultGroupHeaderCountInheritsForegroundThatFollowsStates` - `VerifyTransformResizeAccountsForAncestorScale`, `VerifyTransformResizeSmallWidthChangeWritesOnce`, `VerifyHeaderDisabledByStyleIsNotEnabledOrInvokable` - `VerifySortByColumnRespectsCanSort`, `VerifyImplicitClearKeepsSourceSort`, `VerifyUngatingKeepsAppLitColumnWithoutEvents`, `VerifySortOptOutDuringBeginningEditKeepsTheEditOpen` - `VerifyRecycledRowWithoutCellPeersReusesItsCells`, `VerifyHeaderFocusStaysInHeaderBandWhenColumnsReplaced`, `VerifyBeginEditThatThrowsRollsBackAndAllowsALaterEdit`, `VerifyCellPeerConstructorValidatesArguments` --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 94961bb8-d187-483c-bece-19e203752c67
358 lines
11 KiB
C++
358 lines
11 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
|
|
|
#include "pch.h"
|
|
#include "common.h"
|
|
#include "TableViewColumnHeader.h"
|
|
#include "TableView.h"
|
|
#include "TableViewContextMenu.h"
|
|
#include "TableViewColumnHeaderAutomationPeer.h"
|
|
|
|
static constexpr std::wstring_view s_ContentPresenterPartName{ L"PART_ContentPresenter"sv };
|
|
static constexpr std::wstring_view s_GridLinePartName{ L"PART_GridLine"sv };
|
|
static constexpr std::wstring_view s_SortIndicatorHostPartName{ L"PART_SortIndicatorHost"sv };
|
|
static constexpr std::wstring_view s_ResizeGripperHostPartName{ L"PART_ResizeGripperHost"sv };
|
|
static constexpr std::wstring_view s_SortIndicatorName{ L"TableViewSortIndicator"sv };
|
|
|
|
TableViewColumnHeader::TableViewColumnHeader()
|
|
{
|
|
SetDefaultStyleKey(this);
|
|
}
|
|
|
|
void TableViewColumnHeader::SetOwnerInternal(winrt::TableView const& table, winrt::TableViewColumn const& column)
|
|
{
|
|
m_table = table ? winrt::make_weak(table) : nullptr;
|
|
m_column = column ? winrt::make_weak(column) : nullptr;
|
|
}
|
|
|
|
winrt::AutomationPeer TableViewColumnHeader::OnCreateAutomationPeer()
|
|
{
|
|
auto const table = m_table.get();
|
|
auto const column = m_column.get();
|
|
if (!table || !column)
|
|
{
|
|
return __super::OnCreateAutomationPeer();
|
|
}
|
|
|
|
winrt::AutomationPeer const peer = winrt::make<TableViewColumnHeaderAutomationPeer>(*this, table, column);
|
|
m_automationPeer = winrt::make_weak(peer);
|
|
return peer;
|
|
}
|
|
|
|
void TableViewColumnHeader::OnApplyTemplate()
|
|
{
|
|
__super::OnApplyTemplate();
|
|
|
|
m_isEnabledChangedRevoker.revoke();
|
|
m_templateRootContextRequestedRevoker.revoke();
|
|
|
|
if (auto const oldGripperHost = m_resizeGripperHost.get())
|
|
{
|
|
oldGripperHost.Child(nullptr);
|
|
}
|
|
if (auto const oldIndicatorHost = m_sortIndicatorHost.get())
|
|
{
|
|
oldIndicatorHost.Child(nullptr);
|
|
}
|
|
|
|
m_contentPresenter.set(GetTemplateChild(hstring{ s_ContentPresenterPartName }).try_as<winrt::ContentPresenter>());
|
|
m_gridLine.set(GetTemplateChild(hstring{ s_GridLinePartName }).try_as<winrt::Border>());
|
|
m_sortIndicatorHost.set(GetTemplateChild(hstring{ s_SortIndicatorHostPartName }).try_as<winrt::Border>());
|
|
m_resizeGripperHost.set(GetTemplateChild(hstring{ s_ResizeGripperHostPartName }).try_as<winrt::Border>());
|
|
|
|
m_templateContentMargin = {};
|
|
if (auto const presenter = m_contentPresenter.get())
|
|
{
|
|
m_templateContentMargin = presenter.Margin();
|
|
}
|
|
UpdateContentReserve();
|
|
HostSortIndicator();
|
|
|
|
if (auto const gripperHost = m_resizeGripperHost.get())
|
|
{
|
|
if (auto const gripper = m_resizeGripper.get())
|
|
{
|
|
gripperHost.Child(gripper);
|
|
}
|
|
}
|
|
|
|
// Keeps a Style-set ContextFlyout closed while a cell is editing.
|
|
m_templateRootContextRequestedRevoker = TableViewDetails::HookTemplateRootContextRequested(*this,
|
|
[weakThis = get_weak()](auto&&, winrt::ContextRequestedEventArgs const& args)
|
|
{
|
|
if (auto strongThis = weakThis.get())
|
|
{
|
|
if (auto const owner = strongThis->GetOwningTableView())
|
|
{
|
|
winrt::get_self<TableView>(owner)->SuppressContextRequestWhileEditing(args);
|
|
}
|
|
}
|
|
});
|
|
|
|
m_isEnabledChangedRevoker = IsEnabledChanged(
|
|
winrt::auto_revoke,
|
|
[weakThis = get_weak()](auto const&, auto const&)
|
|
{
|
|
if (auto strongThis = weakThis.get())
|
|
{
|
|
strongThis->UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
});
|
|
|
|
UpdateVisualStates(false /* useTransitions */);
|
|
|
|
if (auto const owner = GetOwningTableView())
|
|
{
|
|
winrt::get_self<TableView>(owner)->OnColumnHeaderTemplateApplied(*this);
|
|
}
|
|
}
|
|
|
|
void TableViewColumnHeader::SetSortableInternal(bool sortable, double indicatorReserve)
|
|
{
|
|
m_isSortable = sortable;
|
|
m_indicatorReserve = sortable ? indicatorReserve : 0.0;
|
|
UpdateContentReserve();
|
|
HostSortIndicator();
|
|
UpdateVisualStates(false /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::SetSortDirectionInternal(winrt::SortDirection direction)
|
|
{
|
|
m_sortDirection = direction;
|
|
if (auto const indicator = m_sortIndicator.get())
|
|
{
|
|
indicator.Direction(TableView::ToSortIndicatorDirection(direction));
|
|
}
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::SetResizeGripperInternal(winrt::ResizeGripper const& gripper)
|
|
{
|
|
if (auto const host = m_resizeGripperHost.get())
|
|
{
|
|
host.Child(gripper);
|
|
}
|
|
m_resizeGripper.set(gripper);
|
|
}
|
|
|
|
winrt::ResizeGripper TableViewColumnHeader::GetResizeGripperInternal() const
|
|
{
|
|
auto const gripper = m_resizeGripper.get();
|
|
auto const host = m_resizeGripperHost.get();
|
|
if (gripper && host && host.Child() == gripper)
|
|
{
|
|
return gripper;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void TableViewColumnHeader::UpdateContentReserve()
|
|
{
|
|
if (auto const presenter = m_contentPresenter.get())
|
|
{
|
|
auto margin = m_templateContentMargin;
|
|
if (m_isSortable)
|
|
{
|
|
margin.Right += m_indicatorReserve;
|
|
}
|
|
if (presenter.Margin() != margin)
|
|
{
|
|
presenter.Margin(margin);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TableViewColumnHeader::HostSortIndicator()
|
|
{
|
|
auto const host = m_sortIndicatorHost.get();
|
|
if (!host)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_isSortable)
|
|
{
|
|
host.Child(nullptr);
|
|
return;
|
|
}
|
|
|
|
auto indicator = m_sortIndicator.get();
|
|
if (!indicator)
|
|
{
|
|
indicator = winrt::SortIndicator{};
|
|
indicator.Name(winrt::hstring{ s_SortIndicatorName });
|
|
indicator.VerticalAlignment(winrt::VerticalAlignment::Center);
|
|
// The header peer already reports the sort state.
|
|
winrt::AutomationProperties::SetAccessibilityView(indicator, winrt::AccessibilityView::Raw);
|
|
m_sortIndicator.set(indicator);
|
|
}
|
|
indicator.Direction(TableView::ToSortIndicatorDirection(m_sortDirection));
|
|
|
|
if (host.Child() != indicator)
|
|
{
|
|
host.Child(indicator);
|
|
}
|
|
}
|
|
|
|
void TableViewColumnHeader::SyncSortIndicatorForeground(bool followText, bool highlighted)
|
|
{
|
|
auto const indicator = m_sortIndicator.get();
|
|
if (!indicator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// The glyph's style sets Foreground, so it cannot inherit; when a state recolors the text
|
|
// (Disabled, or a High Contrast highlight) the glyph must follow it. Bound, not copied, so it
|
|
// also follows a re-resolved theme brush (High Contrast toggle) without a state change.
|
|
auto const presenter = m_contentPresenter.get();
|
|
if (followText && presenter)
|
|
{
|
|
// Rebinding on every state change would rebuild an identical expression.
|
|
auto const bound = indicator.GetBindingExpression(winrt::Control::ForegroundProperty());
|
|
if (!bound || bound.ParentBinding().Source() != presenter)
|
|
{
|
|
winrt::Binding binding;
|
|
binding.Source(presenter);
|
|
binding.Path(winrt::PropertyPath(L"Foreground"));
|
|
indicator.SetBinding(winrt::Control::ForegroundProperty(), binding);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
indicator.ClearValue(winrt::Control::ForegroundProperty());
|
|
}
|
|
|
|
if (highlighted)
|
|
{
|
|
indicator.HighContrastAdjustment(winrt::ElementHighContrastAdjustment::None);
|
|
}
|
|
else
|
|
{
|
|
indicator.ClearValue(winrt::UIElement::HighContrastAdjustmentProperty());
|
|
}
|
|
}
|
|
|
|
void TableViewColumnHeader::UpdateVisualStates(bool useTransitions)
|
|
{
|
|
// Hover and press advertise click-to-sort, so a header that cannot sort stays Normal.
|
|
const wchar_t* commonState = L"Normal";
|
|
if (!IsEnabled())
|
|
{
|
|
commonState = L"Disabled";
|
|
}
|
|
else if (m_isSortable && m_isPressed)
|
|
{
|
|
commonState = L"Pressed";
|
|
}
|
|
else if (m_isSortable && m_isPointerOver)
|
|
{
|
|
commonState = L"PointerOver";
|
|
}
|
|
|
|
const wchar_t* highContrastState = L"HighContrastNormal";
|
|
if (commonState != std::wstring_view{ L"Normal" } && commonState != std::wstring_view{ L"Disabled" })
|
|
{
|
|
if (auto const owner = GetOwningTableView(); owner && winrt::get_self<TableView>(owner)->IsHighContrast())
|
|
{
|
|
highContrastState = m_isPressed ? L"HighContrastPressed" : L"HighContrastPointerOver";
|
|
}
|
|
}
|
|
|
|
// Both groups set the text Foreground, and leaving a state clears it whichever group set it,
|
|
// so the group that sets it must transition last.
|
|
if (IsEnabled())
|
|
{
|
|
winrt::VisualStateManager::GoToState(*this, commonState, useTransitions);
|
|
winrt::VisualStateManager::GoToState(*this, highContrastState, useTransitions);
|
|
}
|
|
else
|
|
{
|
|
winrt::VisualStateManager::GoToState(*this, highContrastState, useTransitions);
|
|
winrt::VisualStateManager::GoToState(*this, commonState, useTransitions);
|
|
}
|
|
const bool highlighted = highContrastState != std::wstring_view{ L"HighContrastNormal" };
|
|
SyncSortIndicatorForeground(highlighted || !IsEnabled(), highlighted);
|
|
|
|
const wchar_t* sortState = L"Unsorted";
|
|
if (m_sortDirection == winrt::SortDirection::Ascending)
|
|
{
|
|
sortState = L"SortAscending";
|
|
}
|
|
else if (m_sortDirection == winrt::SortDirection::Descending)
|
|
{
|
|
sortState = L"SortDescending";
|
|
}
|
|
winrt::VisualStateManager::GoToState(*this, sortState, useTransitions);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerEntered(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerEntered(args);
|
|
m_isPointerOver = true;
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerExited(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerExited(args);
|
|
m_isPointerOver = false;
|
|
m_isPressed = false;
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerPressed(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerPressed(args);
|
|
m_isPressed = true;
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerReleased(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerReleased(args);
|
|
m_isPressed = false;
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerCaptureLost(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerCaptureLost(args);
|
|
ResetPointerState(args);
|
|
}
|
|
|
|
void TableViewColumnHeader::OnPointerCanceled(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
__super::OnPointerCanceled(args);
|
|
ResetPointerState(args);
|
|
}
|
|
|
|
void TableViewColumnHeader::ResetPointerState(winrt::PointerRoutedEventArgs const& args)
|
|
{
|
|
// The 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;
|
|
UpdateVisualStates(true /* useTransitions */);
|
|
}
|
|
|
|
bool TableViewColumnHeader::IsInsideResizeGripper(winrt::IInspectable const& source)
|
|
{
|
|
winrt::DependencyObject 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;
|
|
}
|