From 4cc42747a551827ce862f17a9c6930100ba9f2f0 Mon Sep 17 00:00:00 2001 From: hoshiizumiya Date: Fri, 24 Jul 2026 00:56:00 +0800 Subject: [PATCH 1/3] Refactor DataTable column resizing and layout logic Refactored DataColumn property accessors and resizing methods for clarity and consistency. Improved DataRow layout and TreeView integration, ensuring alignment during resizing and template changes. DataTable now caches resolved column widths and freezes updates during user resizing for consistent layout. Updated IDL and headers to expose new APIs. Enhanced code formatting, comments, and removed obsolete code paths. Improves column resizing accuracy, performance, and maintainability. --- .../DataTable/DataColumn.cpp | 152 ++++- XamlToolkit.Labs.WinUI/DataTable/DataColumn.h | 35 +- .../DataTable/DataColumn.idl | 4 +- XamlToolkit.Labs.WinUI/DataTable/DataRow.cpp | 522 ++++++++++-------- XamlToolkit.Labs.WinUI/DataTable/DataRow.h | 5 +- .../DataTable/DataTable.cpp | 437 +++++++++------ XamlToolkit.Labs.WinUI/DataTable/DataTable.h | 46 +- .../DataTable/DataTable.idl | 12 +- 8 files changed, 755 insertions(+), 458 deletions(-) diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp index b2a38f78..9df8308a 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "winrt_module_imports.h" #include "DataColumn.h" #if __has_include("DataColumn.g.cpp") @@ -9,13 +9,13 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation { - winrt::GridLength DataColumn::DesiredWidth() const - { + winrt::GridLength DataColumn::DesiredWidth() const + { return winrt::unbox_value(GetValue(DesiredWidthProperty())); } - void DataColumn::DesiredWidth(winrt::GridLength value) - { + void DataColumn::DesiredWidth(winrt::GridLength value) + { SetValue(DesiredWidthProperty(), winrt::box_value(value)); } @@ -26,13 +26,13 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation winrt::xaml_typename(), winrt::PropertyMetadata(winrt::box_value(winrt::GridLengthHelper::Auto()), &DataColumn::DesiredWidth_PropertyChanged )); - bool DataColumn::CanResize() const - { - return winrt::unbox_value(GetValue(CanResizeProperty())); + bool DataColumn::CanResize() const + { + return winrt::unbox_value(GetValue(CanResizeProperty())); } - void DataColumn::CanResize(bool value) - { + void DataColumn::CanResize(bool value) + { SetValue(CanResizeProperty(), winrt::box_value(value)); } @@ -50,7 +50,35 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation winrt::GridLength DataColumn::CurrentWidth() const { - return _currentWidth; + return DesiredWidth(); + } + + double DataColumn::ActualColumnWidth() const + { + return _actualColumnWidth; + } + + void DataColumn::SetCurrentWidth(double value) + { + // A user resize resolves Auto/Star columns to pixels. Write that result + // back to the dependency property so XAML, bindings, persistence, and + // the layout engine all observe the same authoritative value. + _isInternalResizeUpdate = true; + try + { + DesiredWidth(GridLengthHelper::FromPixels(value)); + } + catch (...) + { + _isInternalResizeUpdate = false; + throw; + } + _isInternalResizeUpdate = false; + } + + void DataColumn::SetActualColumnWidth(double value) + { + _actualColumnWidth = value; } void DataColumn::OnApplyTemplate() @@ -58,15 +86,19 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation if (_columnSizer) { _columnSizer.TargetControl(nullptr); + _columnSizerManipulationStartedRevoker.revoke(); _columnSizerManipulationDeltaRevoker.revoke(); - _columnSizerManipulationCompletedRevoker.revoke(); + _columnSizerManipulationCompletedRevoker.revoke(); } _columnSizer = GetTemplateChild(PartColumnSizer).try_as(); if (_columnSizer) { - _columnSizer.TargetControl(*this); + // Keep ContentSizer's native manipulation recognizer, but explicitly + // disable its automatic FrameworkElement.Width assignment. + _columnSizer.TargetControl(nullptr); + _columnSizerManipulationStartedRevoker = _columnSizer.ManipulationStarted(winrt::auto_revoke, { this, &DataColumn::ColumnSizer_ManipulationStarted }); _columnSizerManipulationDeltaRevoker = _columnSizer.ManipulationDelta(winrt::auto_revoke, { this, &DataColumn::ColumnSizer_ManipulationDelta }); _columnSizerManipulationCompletedRevoker = _columnSizer.ManipulationCompleted(winrt::auto_revoke, { this, &DataColumn::ColumnSizer_ManipulationCompleted }); } @@ -80,36 +112,106 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation base_type::OnApplyTemplate(); } - void DataColumn::ColumnSizer_ManipulationDelta([[maybe_unused]] winrt::IInspectable const& sender, [[maybe_unused]] winrt::ManipulationDeltaRoutedEventArgs const& e) + void DataColumn::ColumnSizer_ManipulationStarted([[maybe_unused]] winrt::Windows::Foundation::IInspectable const& sender, [[maybe_unused]] ManipulationStartedRoutedEventArgs const& e) { - ColumnResizedByUserSizer(); + auto parent = _parent.get(); + if (parent == nullptr) + { + parent = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this); + if (parent != nullptr) + { + _parent = winrt::make_weak(parent); + } + } + + if (parent == nullptr) + { + return; + } + + auto parentImpl = winrt::get_self(parent); + _resizeStartWidth = parentImpl->BeginColumnResize(*this); + _isManipulationResizing = true; } - void DataColumn::ColumnSizer_ManipulationCompleted([[maybe_unused]] winrt::IInspectable const& sender, [[maybe_unused]] winrt::ManipulationCompletedRoutedEventArgs const& e) + void DataColumn::ColumnSizer_ManipulationDelta([[maybe_unused]] winrt::Windows::Foundation::IInspectable const& sender, winrt::ManipulationDeltaRoutedEventArgs const& e) { - ColumnResizedByUserSizer(); + auto parent = _parent.get(); + if (!_isManipulationResizing || parent == nullptr || _columnSizer == nullptr) + { + return; + } + + const auto dragIncrement = _columnSizer.DragIncrement(); + auto horizontalChange = std::trunc(e.Cumulative().Translation.X / dragIncrement) * dragIncrement; + if (FlowDirection() == winrt::FlowDirection::RightToLeft) + { + horizontalChange *= -1; + } + + auto width = _resizeStartWidth + horizontalChange; + width = std::max(width, MinWidth()); + if (std::isfinite(MaxWidth())) + { + width = std::min(width, MaxWidth()); + } + + const auto currentWidth = DesiredWidth(); + if (currentWidth.GridUnitType != GridUnitType::Pixel || currentWidth.Value != width) + { + SetCurrentWidth(width); + } } - void DataColumn::ColumnResizedByUserSizer() + void DataColumn::ColumnSizer_ManipulationCompleted([[maybe_unused]] winrt::Windows::Foundation::IInspectable const& sender, [[maybe_unused]] winrt::ManipulationCompletedRoutedEventArgs const& e) { - // Update our internal representation to be our size now as a fixed value. - _currentWidth = winrt::GridLength(ActualWidth()); + _isManipulationResizing = false; - // Notify the rest of the table to update if (auto parent = _parent.get()) { - auto parentImpl = winrt::get_self(parent); + // Drag deltas only require arrange. Re-measure once at the end so + // header and row content finalize against the persisted pixel width. + winrt::get_self(parent)->ColumnWidthChanged(); + } + } + + void DataColumn::ApplyDesiredWidth([[maybe_unused]] GridLength const& value) + { + if (!_isInternalResizeUpdate) + { + InvalidateMeasure(); + } + + auto parent = _parent.get(); + if (parent == nullptr) + { + parent = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this); + if (parent == nullptr) + { + return; + } + + _parent = winrt::make_weak(parent); + } + + auto parentImpl = winrt::get_self(parent); + if (_isInternalResizeUpdate) + { parentImpl->ColumnResized(); } + else + { + parentImpl->ColumnWidthChanged(); + } } void DataColumn::DesiredWidth_PropertyChanged(winrt::DependencyObject const& d, [[maybe_unused]] winrt::DependencyPropertyChangedEventArgs const& e) { - // If the developer updates the size of the column, update our internal copy + // The dependency property is the single source of truth. Its callback + // only schedules the required layout work. if (auto col = d.try_as()) { - auto colImpl = winrt::get_self(col); - colImpl->_currentWidth = col.DesiredWidth(); + winrt::get_self(col)->ApplyDesiredWidth(col.DesiredWidth()); } } } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h index 555583ec..ffd009f4 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "DataColumn.g.h" @@ -9,16 +9,16 @@ namespace winrt { - using namespace Windows::Foundation; - using namespace Microsoft::UI::Xaml; - using namespace Microsoft::UI::Xaml::Input; + using namespace Windows::Foundation; + using namespace Microsoft::UI::Xaml; + using namespace Microsoft::UI::Xaml::Input; } namespace winrt::XamlToolkit::Labs::WinUI::implementation { struct DataColumn : DataColumnT { - static constexpr auto PartColumnSizer = L"PART_ColumnSizer"; + static constexpr auto PartColumnSizer = L"PART_ColumnSizer"; DataColumn(); @@ -36,27 +36,38 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation winrt::GridLength CurrentWidth() const; + double ActualColumnWidth() const; + + void SetCurrentWidth(double value); + + void SetActualColumnWidth(double value); + void OnApplyTemplate(); - void ColumnSizer_ManipulationDelta(winrt::IInspectable const& sender, winrt::ManipulationDeltaRoutedEventArgs const& e); + void ColumnSizer_ManipulationStarted(winrt::Windows::Foundation::IInspectable const& sender, winrt::ManipulationStartedRoutedEventArgs const& e); - void ColumnSizer_ManipulationCompleted(winrt::IInspectable const& sender, winrt::ManipulationCompletedRoutedEventArgs const& e); + void ColumnSizer_ManipulationDelta(winrt::Windows::Foundation::IInspectable const& sender, winrt::ManipulationDeltaRoutedEventArgs const& e); - void ColumnResizedByUserSizer(); + void ColumnSizer_ManipulationCompleted(winrt::IInspectable const& sender, winrt::ManipulationCompletedRoutedEventArgs const& e); private: static void DesiredWidth_PropertyChanged(winrt::DependencyObject const& d, winrt::DependencyPropertyChangedEventArgs const& e); - static inline winrt::GridLength StarLength = winrt::GridLength(1, winrt::GridUnitType::Star); + void ApplyDesiredWidth(GridLength const& value); - winrt::GridLength _currentWidth; + double _actualColumnWidth{ 0 }; + bool _isInternalResizeUpdate{ false }; winrt::XamlToolkit::WinUI::Controls::ContentSizer _columnSizer{ nullptr }; winrt::weak_ref _parent; - winrt::UIElement::ManipulationDelta_revoker _columnSizerManipulationDeltaRevoker; - winrt::UIElement::ManipulationCompleted_revoker _columnSizerManipulationCompletedRevoker; + winrt::UIElement::ManipulationStarted_revoker _columnSizerManipulationStartedRevoker; + winrt::UIElement::ManipulationDelta_revoker _columnSizerManipulationDeltaRevoker; + winrt::UIElement::ManipulationCompleted_revoker _columnSizerManipulationCompletedRevoker; + + bool _isManipulationResizing{ false }; + double _resizeStartWidth{ 0 }; }; } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl index c1c77805..a8d99f44 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl @@ -1,4 +1,4 @@ -namespace XamlToolkit.Labs.WinUI +namespace XamlToolkit.Labs.WinUI { [default_interface] runtimeclass DataColumn : Microsoft.UI.Xaml.Controls.ContentControl @@ -12,5 +12,7 @@ namespace XamlToolkit.Labs.WinUI Microsoft.UI.Xaml.GridLength DesiredWidth; static Microsoft.UI.Xaml.DependencyProperty DesiredWidthProperty{ get; }; + + Double ActualColumnWidth{ get; }; } } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataRow.cpp b/XamlToolkit.Labs.WinUI/DataTable/DataRow.cpp index 3bf78cf9..844ace4c 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataRow.cpp +++ b/XamlToolkit.Labs.WinUI/DataTable/DataRow.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "winrt_module_imports.h" #ifdef __INTELLISENSE__ #include @@ -14,243 +14,301 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation { - DataRow::DataRow() - { + DataRow::DataRow() + { Unloaded({ this, &DataRow::DataRow_Unloaded }); - } + } - void DataRow::DataRow_Unloaded([[maybe_unused]] winrt::IInspectable const& sender, [[maybe_unused]] winrt::RoutedEventArgs const& e) - { - // Remove our references on unloaded - if (_parentTable) - { + void DataRow::DataRow_Unloaded([[maybe_unused]] winrt::Windows::Foundation::IInspectable const& sender, [[maybe_unused]] winrt::RoutedEventArgs const& e) + { + // Remove our references on unloaded + if (_parentTable) + { winrt::get_self(_parentTable)->Rows().erase(*this); // Notify table that we may have changed size - _parentTable = nullptr; - } - - _parentPanel = nullptr; - } - - winrt::Panel DataRow::InitializeParentHeaderConnection() - { - // TODO: Think about this expression instead... - // Drawback: Can't have Grid between table and header - // Positive: don't have to restart climbing the Visual Tree if we don't find ItemsPresenter... - ////var parent = this.FindAscendant(static (element) => element is ItemsPresenter or Grid); - - // TODO: Investigate what a scenario with an ItemsRepeater would look like (with a StackLayout, but using DataRow as the item's panel inside) - winrt::Panel panel { nullptr }; - - // 1a. Get parent ItemsPresenter to find header - if (auto itemsPresenter = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this)) - { - // 2. Quickly check if the header is just what we're looking for. - auto header = itemsPresenter.Header(); - if (header.try_as() || header.try_as()) - { - panel = itemsPresenter.Header().try_as(); - } - else - { - // 3. Otherwise, try and find the inner thing we want. - panel = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindDescendant(itemsPresenter, - [](auto&& element) { return element.template try_as() || element.template try_as(); }); - } - - // Check if we're in a TreeView - _isTreeView = static_cast(winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(itemsPresenter).try_as()); - } - - // 1b. If we can't find the ItemsPresenter, then we reach up outside to find the next thing we could use as a parent - if (panel == nullptr) - { - panel = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this, [](auto&& element) - { - return element.template try_as() || element.template try_as(); - }); - } - // Cache actual datatable reference - if (auto table = panel.try_as()) - { - _parentTable = table; - winrt::get_self(_parentTable)->Rows().insert(*this); // Add us to the row list. - } - - return panel; - } - - winrt::Size DataRow::MeasureOverride(winrt::Size availableSize) - { - // We should probably only have to do this once ever? - if (_parentPanel == nullptr) _parentPanel = InitializeParentHeaderConnection(); - - double maxHeight = 0; + _parentTable = nullptr; + } + + _parentPanel = nullptr; + } + + Panel DataRow::InitializeParentHeaderConnection() + { + Panel panel = nullptr; + // TreeView's items are hosted by TreeViewList. The TreeView control itself + // is therefore not guaranteed to be a visual ancestor of the DataRow. + // TreeViewItem is the reliable per-row visual ancestor, including nested + // items created by a hierarchical ItemTemplate. + _isTreeView = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this) != nullptr; + + const auto resolveHeaderPanel = [](winrt::Windows::Foundation::IInspectable const& header) -> Panel + { + if (auto table = header.try_as()) + { + return table; + } + + if (auto headerObject = header.try_as()) + { + if (auto table = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindDescendant(headerObject)) + { + return table; + } + + if (auto grid = header.try_as()) + { + return grid; + } + + return winrt::XamlToolkit::WinUI::DependencyObjectEx::FindDescendant(headerObject); + } + + return nullptr; + }; + + // Resolve HeaderedTreeView directly as well as through ItemsPresenter so + // custom templates can choose where the header is hosted. + if (auto tree = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this)) + { + panel = resolveHeaderPanel(tree.Header()); + } + + // A nested TreeViewItem can add another ItemsPresenter between the row and the + // TreeViewList. Preserve support for controls whose header remains in their + // ItemsPresenter. + for (const auto& ancestor : winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendants(*this)) + { + if (panel != nullptr) break; + + auto itemsPresenter = ancestor.try_as(); + if (itemsPresenter == nullptr || itemsPresenter.Header() == nullptr) + { + continue; + } + + panel = resolveHeaderPanel(itemsPresenter.Header()); + } + + // Preserve the Grid hybrid scenario, but do not mistake TreeViewItem's + // MultiSelectGrid for the row's column definition source. + if (panel == nullptr && !_isTreeView) + { + panel = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this, + [](auto&& element) { return element.template try_as() || element.template try_as(); }); + } + + // Cache actual datatable reference + if (auto table = panel.try_as()) + { + _parentTable = table; + winrt::get_self(_parentTable)->Rows().insert(*this); // Add us to the row list. + } + + return panel; + } + + double DataRow::GetTreePadding() + { + if (!_isTreeView) + { + return 0; + } + + auto container = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this, L"MultiSelectGrid").try_as(); + if (container == nullptr) + { + return 0; + } + + // Account for the fixed outer presenter inset before applying the + // depth-dependent MultiSelectGrid indentation. + double padding = container.Margin().Left + container.BorderThickness().Left + container.Padding().Left; + if (auto presenter = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this, L"ContentPresenterGrid").try_as()) + { + padding += presenter.Margin().Left + presenter.BorderThickness().Left + presenter.Padding().Left; + } + auto definitions = container.ColumnDefinitions(); + if (definitions.Size() > 1) + { + // DataRow is hosted in the final content column. The preceding template + // columns contain selection and expand/collapse affordances. + for (uint32_t i = 0; i + 1 < definitions.Size(); i++) + { + padding += definitions.GetAt(i).ActualWidth(); + } + } + else + { + // Fallback for a customized TreeViewItem template without column + // definitions. + auto containerChildren = container.Children(); + for (uint32_t i = 0; i + 1 < containerChildren.Size(); i++) + { + padding += containerChildren.GetAt(i).DesiredSize().Width; + } + } + + return padding; + } + + Size DataRow::MeasureOverride(Size availableSize) + { + // We should probably only have to do this once ever? + if (_parentPanel == nullptr) _parentPanel = InitializeParentHeaderConnection(); + + double maxHeight = 0; auto children = Children(); - uint32_t childCount = children.Size(); - - if (childCount > 0) - { - // If we don't have a grid, just measure first child to get row height and take available space - if (_parentPanel == nullptr) - { - auto first = children.GetAt(0); - first.Measure(availableSize); - return winrt::Size(availableSize.Width, first.DesiredSize().Height); - } - // Handle DataTable Parent - else if (_parentTable - && _parentTable.Children().Size() == childCount) - { - // TODO: Need to check visibility - // Measure all children since we need to determine the row's height at minimum - for (uint32_t i = 0; i < childCount; i++) - { - auto childElement = children.GetAt(i); - auto dataColumn = _parentTable.Children().GetAt(i).try_as(); + uint32_t childCount = children.Size(); + + if (childCount > 0) + { + // If we don't have a grid, just measure first child to get row height and take available space + if (_parentPanel == nullptr) + { + children.GetAt(0).Measure(availableSize); + return Size(availableSize.Width, children.GetAt(0).DesiredSize().Height); + } + // Handle DataTable Parent + else if (_parentTable != nullptr + && _parentTable.Children().Size() == childCount) + { + // TODO: Need to check visibility + // Measure all children since we need to determine the row's height at minimum + for (uint32_t i = 0; i < childCount; i++) + { + auto childElement = children.GetAt(i); + auto dataColumn = _parentTable.Children().GetAt(i).try_as(); if (dataColumn == nullptr) continue; - auto colImpl = winrt::get_self(dataColumn); - - if (colImpl->CurrentWidth().GridUnitType == winrt::GridUnitType::Auto) - { - childElement.Measure(availableSize); - - // For TreeView in the first column, we want the header to expand to encompass - // the maximum indentation of the tree. - double padding = 0; - //// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...? - if (i == 0 && _isTreeView) - { - // Get our containing grid from TreeViewItem, start with our indented padding - auto parentContainer = winrt::XamlToolkit::WinUI::DependencyObjectEx::FindAscendant(*this, L"MultiSelectGrid").try_as(); - if (parentContainer) - { - _treePadding = parentContainer.Padding().Left; - // We assume our 'DataRow' is in the last child slot of the Grid, need to know how large the other columns are. - auto containerChildren = parentContainer.Children(); - uint32_t containerChildrenCount = containerChildren.Size(); - for (int j = 0; j < static_cast(containerChildrenCount) - 1; j++) - { - // TODO: We may need to get the actual size here later in Arrange? - _treePadding += containerChildren.GetAt(j).DesiredSize().Width; - } - } - padding = _treePadding; - } - - // TODO: Do we want this to ever shrink back? - auto& prev = colImpl->MaxChildDesiredWidth; - colImpl->MaxChildDesiredWidth = std::max(colImpl->MaxChildDesiredWidth, childElement.DesiredSize().Width + padding); - if (colImpl->MaxChildDesiredWidth != prev) - { - // If our measure has changed, then we have to invalidate the arrange of the DataTable - winrt::get_self(_parentTable)->ColumnResized(); - } - - } - else if (colImpl->CurrentWidth().GridUnitType == winrt::GridUnitType::Pixel) - { - childElement.Measure(winrt::Size(static_cast(colImpl->DesiredWidth().Value), availableSize.Height)); - } - else - { - childElement.Measure(availableSize); - } - - maxHeight = std::max(maxHeight, childElement.DesiredSize().Height); - } - } - // Fallback for Grid Hybrid scenario... - else if (auto grid = _parentPanel.try_as(); - grid && _parentPanel.Children().Size() == childCount - && grid.ColumnDefinitions().Size() == Children().Size()) - { - // TODO: Need to check visibility - // Measure all children since we need to determine the row's height at minimum - for (uint32_t i = 0; i < childCount; i++) - { - auto childElement = children.GetAt(i); + auto colImpl = winrt::get_self(dataColumn); + + if (colImpl->CurrentWidth().GridUnitType == GridUnitType::Auto) + { + childElement.Measure(availableSize); + + // TODO: Do we want this to ever shrink back? + auto& prev = colImpl->MaxChildDesiredWidth; + colImpl->MaxChildDesiredWidth = std::max(colImpl->MaxChildDesiredWidth, childElement.DesiredSize().Width); + if (colImpl->MaxChildDesiredWidth != prev) + { + // If our measure has changed, then we have to invalidate the arrange of the DataTable + winrt::get_self(_parentTable)->ColumnResized(); + } + + } + else if (colImpl->CurrentWidth().GridUnitType == GridUnitType::Pixel) + { + childElement.Measure(Size(static_cast(colImpl->CurrentWidth().Value), availableSize.Height)); + } + else + { + childElement.Measure(availableSize); + } + + maxHeight = std::max(maxHeight, childElement.DesiredSize().Height); + } + } + // Fallback for Grid Hybrid scenario... + else if (auto grid = _parentPanel.try_as(); + grid && _parentPanel.Children().Size() == childCount + && grid.ColumnDefinitions().Size() == Children().Size()) + { + // TODO: Need to check visibility + // Measure all children since we need to determine the row's height at minimum + for (uint32_t i = 0; i < childCount; i++) + { + auto childElement = children.GetAt(i); auto colDef = grid.ColumnDefinitions().GetAt(i); - if (colDef.Width().GridUnitType == winrt::GridUnitType::Pixel) - { - childElement.Measure(winrt::Size(static_cast(colDef.Width().Value), availableSize.Height)); - } - else - { - childElement.Measure(availableSize); - } - - maxHeight = std::max(maxHeight, childElement.DesiredSize().Height); - } - } - // TODO: What do we want to do if there's unequal children in the DataTable vs. DataRow? - } - - // Otherwise, return our parent's size as the desired size. - return winrt::Size(_parentPanel ? _parentPanel.DesiredSize().Width : availableSize.Width, static_cast(maxHeight)); - } - - winrt::Size DataRow::ArrangeOverride(winrt::Size finalSize) - { - uint32_t column = 0; - double x = 0; - - // Try and grab Column Spacing from DataTable, if not a parent Grid, if not 0. - double spacing = 0.0; - - if (_parentTable) - { - spacing = _parentTable.ColumnSpacing(); - } - else if (auto grid = _parentPanel.try_as()) - { - spacing = grid.ColumnSpacing(); - } - - double width = 0; - - if (_parentPanel) - { - int i = 0; - auto elements = Children() - | std::ranges::views::filter([](auto&& e) { return e.Visibility() == winrt::Visibility::Visible; }); - - for (const auto& child : elements) - { - if (auto grid = _parentPanel.try_as(); grid && - column < grid.ColumnDefinitions().Size()) - { - width = grid.ColumnDefinitions().GetAt(column++).ActualWidth(); - } - // TODO: Need to check Column visibility here as well... - else - { - if (auto table = _parentPanel.try_as(); table && column < table.Children().Size()) { - // TODO: This is messy... - auto col = table.Children().GetAt(column++).try_as(); - width = (col) ? col.ActualWidth() : 0; - } - } - - // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, then the DataColumn size we grab above should account for the largest of our children. - if (i == 0) - { - child.Arrange(winrt::Rect(static_cast(x), 0, static_cast(width), finalSize.Height)); - } - else - { - // If we're in a tree, remove the indentation from the layout of columns beyond the first. - child.Arrange(winrt::Rect(static_cast(x - _treePadding), 0, static_cast(width), finalSize.Height)); - } - - x += width + spacing; - i++; - } - - return winrt::Size(static_cast(x - spacing), finalSize.Height); - } - - return finalSize; - } + if (colDef.Width().GridUnitType == GridUnitType::Pixel) + { + childElement.Measure(Size(static_cast(colDef.Width().Value), availableSize.Height)); + } + else + { + childElement.Measure(availableSize); + } + + maxHeight = std::max(maxHeight, childElement.DesiredSize().Height); + } + } + // TODO: What do we want to do if there's unequal children in the DataTable vs. DataRow? + } + + // Fill the width offered by the item container. Returning the header's desired + // width makes the entire row move when resized columns no longer fill the table. + auto desiredWidth = availableSize.Width; + if (!std::isfinite(desiredWidth)) + { + desiredWidth = _parentPanel + ? _parentPanel.DesiredSize().Width + : 0; + } + + return winrt::Size(desiredWidth, static_cast(maxHeight)); + } + + winrt::Size DataRow::ArrangeOverride(winrt::Size finalSize) + { + uint32_t column = 0; + // Use only the current TreeViewItem template's local layout data. A global + // transform can still describe a recycled container's previous position + // while its new Arrange pass is running. + double x = -GetTreePadding(); + + // Try and grab Column Spacing from DataTable, if not a parent Grid, if not 0. + double spacing = 0.0; + + if (_parentTable) + { + spacing = _parentTable.ColumnSpacing(); + } + else if (auto grid = _parentPanel.try_as()) + { + spacing = grid.ColumnSpacing(); + } + + double width = 0; + + if (_parentPanel != nullptr) + { + int i = 0; + auto elements = Children() + | std::ranges::views::filter([](auto&& e) { return e.Visibility() == Visibility::Visible; }); + + for (const UIElement& child : elements) + { + if (auto grid = _parentPanel.try_as(); grid && + column < grid.ColumnDefinitions().Size()) + { + width = grid.ColumnDefinitions().GetAt(column++).ActualWidth(); + } + // TODO: Need to check Column visibility here as well... + else + { + if (auto table = _parentPanel.try_as(); table && column < table.Children().Size()) { + auto tableImpl = winrt::get_self(table); + width = tableImpl->ColumnWidth(column++); + } + } + + // DataRow itself begins after TreeViewItem's indentation. Its first + // cell cannot start before that origin, but its right edge and every + // following column use the header's exact coordinates. + const auto arrangeX = i == 0 && _isTreeView ? 0 : x; + const auto arrangeWidth = i == 0 && _isTreeView + ? std::max(x + width, 0) + : width; + child.Arrange(Rect(static_cast(arrangeX), 0, static_cast(arrangeWidth), finalSize.Height)); + x += width + spacing; + i++; + } + + // The row must keep the full slot assigned by the item container. Returning + // only the sum of column widths lets TreeViewItem realign the entire row + // whenever resized columns no longer fill the viewport. + return finalSize; + } + + return finalSize; + } } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataRow.h b/XamlToolkit.Labs.WinUI/DataTable/DataRow.h index a71aa2e4..68bbba27 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataRow.h +++ b/XamlToolkit.Labs.WinUI/DataTable/DataRow.h @@ -22,13 +22,14 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation private: winrt::Panel InitializeParentHeaderConnection(); - void DataRow_Unloaded(winrt::IInspectable const& sender, winrt::RoutedEventArgs const& e); + double GetTreePadding(); + + void DataRow_Unloaded(winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& e); winrt::Panel _parentPanel{ nullptr }; winrt::XamlToolkit::Labs::WinUI::DataTable _parentTable{ nullptr }; bool _isTreeView{ false }; - double _treePadding{ 0.0 }; }; } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataTable.cpp b/XamlToolkit.Labs.WinUI/DataTable/DataTable.cpp index bd17c27d..9ec11071 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataTable.cpp +++ b/XamlToolkit.Labs.WinUI/DataTable/DataTable.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "winrt_module_imports.h" #ifdef __INTELLISENSE__ #include @@ -13,169 +13,280 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation { - bool DataTable::IsAnyColumnAuto() - { - auto children = Children(); - return std::any_of(children.begin(), children.end(), [](auto&& e) - { - if (auto column = e.template try_as()) - { - auto columnImpl = winrt::get_self(column); - return columnImpl->CurrentWidth().GridUnitType == winrt::GridUnitType::Auto; - } - - return false; - }); - } + bool DataTable::IsAnyColumnAuto() + { + auto children = Children(); + return std::any_of(children.begin(), children.end(), [](auto&& e) + { + if (auto column = e.template try_as()) { + auto columnImpl = winrt::get_self(column); + return columnImpl->CurrentWidth().GridUnitType == GridUnitType::Auto; + } + + return false; + }); + } std::set& DataTable::Rows() { return _rows; } - void DataTable::ColumnResized() - { - InvalidateArrange(); - - for (const auto& row : Rows()) - { - row.InvalidateArrange(); - } - } - - bool DataTable::ColumnSpacing() const { return winrt::unbox_value(GetValue(ColumnSpacingProperty())); } - void DataTable::ColumnSpacing(double value) { SetValue(ColumnSpacingProperty(), winrt::box_value(value)); } - - const wil::single_threaded_property DataTable::ColumnSpacingProperty = - winrt::DependencyProperty::Register( - L"ColumnSpacing", - winrt::xaml_typename(), - winrt::xaml_typename(), - winrt::PropertyMetadata(winrt::box_value(0.0))); - - winrt::Size DataTable::MeasureOverride(winrt::Size availableSize) - { - double fixedWidth = 0; - double proportionalUnits = 0; - double autoSized = 0; - - double maxHeight = 0; - - auto elements = Children() - | std::ranges::views::filter([](auto&& e) { return e.Visibility() == winrt::Visibility::Visible && e.template try_as(); }) - | std::ranges::views::transform([](auto&& e) { return e.template as(); }) - | std::ranges::to(); - - // We only need to measure elements that are visible - for (const auto& column : elements) - { - auto columnImpl = winrt::get_self(column); - if (winrt::GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) - { - proportionalUnits += columnImpl->DesiredWidth().Value; - } - else if (winrt::GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) - { - fixedWidth += columnImpl->DesiredWidth().Value; - } - } - - // Add in spacing between columns to our fixed size allotment - fixedWidth += (elements.size() - 1) * ColumnSpacing(); - - // TODO: Handle infinite width? - auto proportionalAmount = (availableSize.Width - fixedWidth) / proportionalUnits; - - for (const auto& column : elements) - { - auto columnImpl = winrt::get_self(column); - if (winrt::GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) - { - column.Measure(winrt::Size(static_cast(proportionalAmount * columnImpl->CurrentWidth().Value), availableSize.Height)); - } - else if (winrt::GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) - { - column.Measure(winrt::Size(static_cast(columnImpl->CurrentWidth().Value), availableSize.Height)); - } - else - { - // TODO: Technically this is using 'Auto' on the Header content - // What the developer probably intends is it to be adjusted based on the contents of the rows... - // To enable this scenario, we'll need to actually measure the contents of the rows for that column - // in DataRow and figure out the maximum size to report back and adjust here in some sort of hand-shake - // for the layout process... (i.e. get the data in the measure step, use it in the arrange step here, - // then invalidate the child arranges [don't re-measure and cause loop]...) - - // For now, we'll just use the header content as a guideline to see if things work. - - // Avoid negative values when columns don't fit `availableSize`. Otherwise the `Size` constructor will throw. - column.Measure(winrt::Size(std::max(static_cast(availableSize.Width - fixedWidth - autoSized), 0), availableSize.Height)); - - // Keep track of already 'allotted' space, use either the maximum child size (if we know it) or the header content - autoSized += std::max(column.DesiredSize().Width, columnImpl->MaxChildDesiredWidth); - } - - maxHeight = std::max(maxHeight, column.DesiredSize().Height); - } - - return winrt::Size(availableSize.Width, static_cast(maxHeight)); - } - - winrt::Size DataTable::ArrangeOverride(winrt::Size finalSize) - { - double fixedWidth = 0; - double proportionalUnits = 0; - double autoSized = 0; - - auto elements = Children() - | std::ranges::views::filter([](auto&& e) { return e.Visibility() == winrt::Visibility::Visible && e.template try_as(); }) - | std::ranges::views::transform([](auto&& e) { return e.template as(); }); - - // We only need to measure elements that are visible - for (const auto& column : elements) - { - auto columnImpl = winrt::get_self(column); - if (winrt::GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) - { - proportionalUnits += columnImpl->CurrentWidth().Value; - } - else if (winrt::GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) - { - fixedWidth += columnImpl->CurrentWidth().Value; - } - else - { - autoSized += std::max(columnImpl->DesiredSize().Width, columnImpl->MaxChildDesiredWidth); - } - } - - // TODO: Handle infinite width? - // TODO: This can go out of bounds or something around here when pushing a resized column to the right... - auto proportionalAmount = (finalSize.Width - fixedWidth - autoSized) / proportionalUnits; - - double width = 0; - double x = 0; - - for (const auto& column : elements) - { - auto columnImpl = winrt::get_self(column); - if (winrt::GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) - { - width = proportionalAmount * columnImpl->CurrentWidth().Value; - column.Arrange(winrt::Rect(static_cast(x), 0, static_cast(width), finalSize.Height)); - } - else if (winrt::GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) - { - width = columnImpl->CurrentWidth().Value; - column.Arrange(winrt::Rect(static_cast(x), 0, static_cast(width), finalSize.Height)); - } - else - { - // TODO: We use the comparison of sizes a lot, should we cache in the DataColumn itself? - width = std::max(column.DesiredSize().Width, columnImpl->MaxChildDesiredWidth); - column.Arrange(winrt::Rect(static_cast(x), 0, static_cast(width), finalSize.Height)); - } - - x += width + ColumnSpacing(); - } - - return finalSize; - } + double DataTable::ColumnWidth(uint32_t index) const + { + return index < _columnWidths.size() ? _columnWidths[index] : 0; + } + + double DataTable::BeginColumnResize(winrt::XamlToolkit::Labs::WinUI::DataColumn const& resizedColumn) + { + const auto width = _layoutWidth > 0 ? _layoutWidth : ActualWidth(); + UpdateColumnWidths(width); + const auto resolvedWidths = _columnWidths; + + double resizedWidth = resizedColumn.ActualWidth(); + auto children = Children(); + _isFreezingColumnWidths = true; + try + { + for (uint32_t i = 0; i < children.Size(); i++) + { + auto column = children.GetAt(i).try_as(); + if (column == nullptr || column.Visibility() != Visibility::Visible) + { + continue; + } + + const auto resolvedWidth = i < resolvedWidths.size() ? resolvedWidths[i] : 0; + winrt::get_self(column)->SetCurrentWidth(resolvedWidth); + if (column == resizedColumn) + { + resizedWidth = resolvedWidth; + } + } + } + catch (...) + { + _isFreezingColumnWidths = false; + throw; + } + _isFreezingColumnWidths = false; + + // Freezing all currently resolved widths prevents a Star column from + // compensating in the opposite direction while another column is dragged. + UpdateColumnWidths(width); + return resizedWidth; + } + + void DataTable::ColumnWidthChanged() + { + if (_isFreezingColumnWidths) + { + return; + } + + const auto width = _layoutWidth > 0 ? _layoutWidth : ActualWidth(); + UpdateColumnWidths(width); + InvalidateMeasure(); + InvalidateArrange(); + + for (auto& row : Rows()) + { + row.InvalidateMeasure(); + row.InvalidateArrange(); + } + } + + void DataTable::UpdateColumnWidths(double availableWidth) + { + auto children = Children(); + _columnWidths.assign(children.Size(), 0); + + double fixedWidth = 0; + double autoWidth = 0; + double proportionalUnits = 0; + uint32_t visibleColumns = 0; + + for (uint32_t i = 0; i < children.Size(); i++) + { + auto column = children.GetAt(i).try_as(); + if (column == nullptr || column.Visibility() != Visibility::Visible) continue; + + visibleColumns++; + auto columnImpl = winrt::get_self(column); + const auto currentWidth = columnImpl->CurrentWidth(); + + if (GridLengthHelper::GetIsAbsolute(currentWidth)) + { + _columnWidths[i] = currentWidth.Value; + fixedWidth += currentWidth.Value; + } + else if (GridLengthHelper::GetIsStar(currentWidth)) + { + proportionalUnits += currentWidth.Value; + } + else + { + const auto width = std::max(column.DesiredSize().Width, columnImpl->MaxChildDesiredWidth); + _columnWidths[i] = width; + autoWidth += width; + } + } + + const auto spacingWidth = visibleColumns > 1 + ? (visibleColumns - 1) * ColumnSpacing() + : 0; + const auto proportionalAmount = proportionalUnits > 0 && std::isfinite(availableWidth) + ? std::max((availableWidth - fixedWidth - autoWidth - spacingWidth) / proportionalUnits, 0) + : 0; + + for (uint32_t i = 0; i < children.Size(); i++) + { + auto column = children.GetAt(i).try_as(); + if (column == nullptr || column.Visibility() != Visibility::Visible) continue; + + auto columnImpl = winrt::get_self(column); + if (GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) + { + _columnWidths[i] = proportionalAmount * columnImpl->CurrentWidth().Value; + } + } + + for (uint32_t i = 0; i < children.Size(); i++) + { + if (auto column = children.GetAt(i).try_as(); + column != nullptr && column.Visibility() == Visibility::Visible) + { + winrt::get_self(column)->SetActualColumnWidth(_columnWidths[i]); + } + } + } + + void DataTable::ColumnResized() + { + if (_isFreezingColumnWidths) + { + return; + } + + UpdateColumnWidths(_layoutWidth); + InvalidateArrange(); + + for (auto& row : Rows()) + { + row.InvalidateArrange(); + } + } + + double DataTable::ColumnSpacing() const { return winrt::unbox_value(GetValue(ColumnSpacingProperty)); } + void DataTable::ColumnSpacing(double value) { SetValue(ColumnSpacingProperty, winrt::box_value(value)); } + + const wil::single_threaded_property DataTable::ColumnSpacingProperty = + DependencyProperty::Register(L"ColumnSpacing", winrt::xaml_typename(), winrt::xaml_typename(), PropertyMetadata(winrt::box_value(0.0))); + + Size DataTable::MeasureOverride(Size availableSize) + { + _layoutWidth = availableSize.Width; + + double fixedWidth = 0; + double proportionalUnits = 0; + double autoSized = 0; + + double maxHeight = 0; + + auto elements = Children() + | std::ranges::views::filter([](auto&& e) { return e.Visibility() == Visibility::Visible && e.template try_as(); }) + | std::ranges::views::transform([](auto&& e) { return e.template as(); }) + | std::ranges::to(); + + // We only need to measure elements that are visible + for (const auto& column : elements) + { + auto columnImpl = winrt::get_self(column); + if (GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) + { + proportionalUnits += columnImpl->CurrentWidth().Value; + } + else if (GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) + { + fixedWidth += columnImpl->CurrentWidth().Value; + } + } + + // Add in spacing between columns to our fixed size allotment + if (elements.size() > 1) + { + fixedWidth += (elements.size() - 1) * ColumnSpacing(); + } + + // TODO: Handle infinite width? + const auto proportionalAmount = proportionalUnits > 0 + ? std::max((availableSize.Width - fixedWidth) / proportionalUnits, 0) + : 0; + + for (const auto& column : elements) + { + auto columnImpl = winrt::get_self(column); + if (GridLengthHelper::GetIsStar(columnImpl->CurrentWidth())) + { + column.Measure(Size(static_cast(proportionalAmount * columnImpl->CurrentWidth().Value), availableSize.Height)); + } + else if (GridLengthHelper::GetIsAbsolute(columnImpl->CurrentWidth())) + { + column.Measure(Size(static_cast(columnImpl->CurrentWidth().Value), availableSize.Height)); + } + else + { + // TODO: Technically this is using 'Auto' on the Header content + // What the developer probably intends is it to be adjusted based on the contents of the rows... + // To enable this scenario, we'll need to actually measure the contents of the rows for that column + // in DataRow and figure out the maximum size to report back and adjust here in some sort of hand-shake + // for the layout process... (i.e. get the data in the measure step, use it in the arrange step here, + // then invalidate the child arranges [don't re-measure and cause loop]...) + + // For now, we'll just use the header content as a guideline to see if things work. + + // Avoid negative values when columns don't fit `availableSize`. Otherwise the `Size` constructor will throw. + column.Measure(Size(std::max(static_cast(availableSize.Width - fixedWidth - autoSized), 0), availableSize.Height)); + + // Keep track of already 'allotted' space, use either the maximum child size (if we know it) or the header content + autoSized += std::max(column.DesiredSize().Width, columnImpl->MaxChildDesiredWidth); + } + + maxHeight = std::max(maxHeight, column.DesiredSize().Height); + } + + UpdateColumnWidths(availableSize.Width); + + return Size(availableSize.Width, static_cast(maxHeight)); + } + + Size DataTable::ArrangeOverride(Size finalSize) + { + _layoutWidth = finalSize.Width; + UpdateColumnWidths(finalSize.Width); + + double x = 0; + auto children = Children(); + uint32_t arrangedColumns = 0; + const auto visibleColumns = std::ranges::count_if(children, + [](auto&& element) + { + return element.Visibility() == Visibility::Visible && + element.template try_as() != nullptr; + }); + + for (uint32_t i = 0; i < children.Size(); i++) + { + auto column = children.GetAt(i).try_as(); + if (column == nullptr || column.Visibility() != Visibility::Visible) continue; + + const auto width = ColumnWidth(i); + column.Arrange(Rect(static_cast(x), 0, static_cast(width), finalSize.Height)); + x += width; + + if (++arrangedColumns < visibleColumns) + { + x += ColumnSpacing(); + } + } + + return finalSize; + } } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataTable.h b/XamlToolkit.Labs.WinUI/DataTable/DataTable.h index a5344d8d..f30627bb 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataTable.h +++ b/XamlToolkit.Labs.WinUI/DataTable/DataTable.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "DataTable.g.h" @@ -8,6 +8,7 @@ #include #include #include +#include #endif namespace winrt @@ -18,21 +19,27 @@ namespace winrt namespace winrt::XamlToolkit::Labs::WinUI::implementation { - struct DataTable : DataTableT - { - DataTable() = default; + struct DataTable : DataTableT + { + DataTable() = default; - // TODO: We should cache this result and update if column properties change - bool IsAnyColumnAuto(); + // TODO: We should cache this result and update if column properties change + bool IsAnyColumnAuto(); - // TODO: Check with Sergio if there's a better structure here, as I don't need a Dictionary like ConditionalWeakTable - std::set& Rows(); + // TODO: Check with Sergio if there's a better structure here, as I don't need a Dictionary like ConditionalWeakTable + std::set& Rows(); - void ColumnResized(); + double ColumnWidth(uint32_t index) const; - //// TODO: Would we want this named 'Spacing' instead if we support an Orientation in the future for columns being items instead of rows? - bool ColumnSpacing() const; - void ColumnSpacing(double value); + double BeginColumnResize(winrt::XamlToolkit::Labs::WinUI::DataColumn const& column); + + void ColumnWidthChanged(); + + void ColumnResized(); + + //// TODO: Would we want this named 'Spacing' instead if we support an Orientation in the future for columns being items instead of rows? + double ColumnSpacing() const; + void ColumnSpacing(double value); static const wil::single_threaded_property ColumnSpacingProperty; @@ -40,14 +47,19 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation winrt::Size ArrangeOverride(winrt::Size finalSize); - private: + private: + void UpdateColumnWidths(double availableWidth); + std::set _rows; - }; + std::vector _columnWidths; + double _layoutWidth{ 0 }; + bool _isFreezingColumnWidths{ false }; + }; } namespace winrt::XamlToolkit::Labs::WinUI::factory_implementation { - struct DataTable : DataTableT - { - }; + struct DataTable : DataTableT + { + }; } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataTable.idl b/XamlToolkit.Labs.WinUI/DataTable/DataTable.idl index f743991d..5253903c 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataTable.idl +++ b/XamlToolkit.Labs.WinUI/DataTable/DataTable.idl @@ -1,11 +1,11 @@ -namespace XamlToolkit.Labs.WinUI +namespace XamlToolkit.Labs.WinUI { runtimeclass DataTable : Microsoft.UI.Xaml.Controls.Panel - { - DataTable(); + { + DataTable(); - Double ColumnSpacing; + Double ColumnSpacing; - static Microsoft.UI.Xaml.DependencyProperty ColumnSpacingProperty{ get; }; - } + static Microsoft.UI.Xaml.DependencyProperty ColumnSpacingProperty{ get; }; + } } From 9260b63659526b34bdbf23e3d08313a7f35dc8f8 Mon Sep 17 00:00:00 2001 From: hoshiizumiya Date: Sat, 25 Jul 2026 16:48:39 +0800 Subject: [PATCH 2/3] [DataTable]: Add ColumnSizerStyle for user's custom --- .../DataTable/DataColumn.cpp | 16 ++++++++++ XamlToolkit.Labs.WinUI/DataTable/DataColumn.h | 5 ++++ .../DataTable/DataColumn.idl | 4 +++ .../DataTable/DataColumn.xaml | 29 +++++++++++++------ 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp index 9df8308a..ab32b077 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.cpp @@ -26,6 +26,22 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation winrt::xaml_typename(), winrt::PropertyMetadata(winrt::box_value(winrt::GridLengthHelper::Auto()), &DataColumn::DesiredWidth_PropertyChanged )); + winrt::Microsoft::UI::Xaml::Style DataColumn::ColumnSizerStyle() const + { + return GetValue(ColumnSizerStyleProperty).try_as(); + } + + void DataColumn::ColumnSizerStyle(winrt::Microsoft::UI::Xaml::Style const& value) + { + SetValue(ColumnSizerStyleProperty, value); + } + + const wil::single_threaded_property DataColumn::ColumnSizerStyleProperty = + DependencyProperty::Register(L"ColumnSizerStyle", + winrt::xaml_typename(), + winrt::xaml_typename(), + PropertyMetadata(nullptr)); + bool DataColumn::CanResize() const { return winrt::unbox_value(GetValue(CanResizeProperty())); diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h index ffd009f4..e8eb6bea 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.h @@ -32,6 +32,11 @@ namespace winrt::XamlToolkit::Labs::WinUI::implementation static const wil::single_threaded_property DesiredWidthProperty; + winrt::Microsoft::UI::Xaml::Style ColumnSizerStyle() const; + void ColumnSizerStyle(winrt::Microsoft::UI::Xaml::Style const& value); + + static const wil::single_threaded_property ColumnSizerStyleProperty; + wil::single_threaded_rw_property MaxChildDesiredWidth; winrt::GridLength CurrentWidth() const; diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl index a8d99f44..6db78ad7 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.idl @@ -13,6 +13,10 @@ static Microsoft.UI.Xaml.DependencyProperty DesiredWidthProperty{ get; }; + Microsoft.UI.Xaml.Style ColumnSizerStyle; + + static Microsoft.UI.Xaml.DependencyProperty ColumnSizerStyleProperty{ get; }; + Double ActualColumnWidth{ get; }; } } diff --git a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.xaml b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.xaml index d262e325..fd24e97f 100644 --- a/XamlToolkit.Labs.WinUI/DataTable/DataColumn.xaml +++ b/XamlToolkit.Labs.WinUI/DataTable/DataColumn.xaml @@ -1,13 +1,28 @@ - + + xmlns:converters="using:XamlToolkit.WinUI.Converters" + xmlns:labs="using:XamlToolkit.Labs.WinUI"> + + + + + +