簡單在 WinUI 仿造 WPF 的 ColumnDefinition SharedSizeGroup 共享列寬功能

lindexi發表於2024-08-11

本文將告訴大家如何在 WinUI 3 或 UNO 裡面,仿造 WPF 的 ColumnDefinition SharedSizeGroup 共享列寬功能

本文的實現程式碼是大量從 https://github.com/Qiu233/WinUISharedSizeGroup 抄的,感謝大佬提供的程式碼。我在此基礎上簡化了對 Behavior 的依賴,在本文末尾放上了全部程式碼的下載方法

實現效果如下:

在介面放入兩個 Grid 容器,這兩個 Grid 容器分別都有兩列,其中第零個 Grid 裡面的首列放入一個帶背景的 Border 控制元件,預設情況下寬度被壓縮,期望能透過 SharedSizeGroup 的能力共享其他 Grid 的列寬而被撐開。第一個 Grid 裡面的首列放入一個按鈕,按鈕點選的時候修改按鈕的寬度,程式碼如下

  <Grid local:ColumnSharedSizeHelper.IsSharedSizeScope="true">
    <Grid.RowDefinitions>
      <RowDefinition Height="100"></RowDefinition>
      <RowDefinition Height="100"></RowDefinition>
    </Grid.RowDefinitions>

    <Grid x:Name="Grid1">
      <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
      </Grid.ColumnDefinitions>
      <Border Background="Blue" local:ColumnSharedSizeHelper.SharedSizeGroup="S1"></Border>
    </Grid>
    <Grid Grid.Row="1">
      <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
      </Grid.ColumnDefinitions>
      <Button Width="100" local:ColumnSharedSizeHelper.SharedSizeGroup="S1" Click="Button_OnClick"/>
    </Grid>
  </Grid>

如以上程式碼可以看到新增了名為 ColumnSharedSizeHelper 的輔助類用來提供 IsSharedSizeScope 和 SharedSizeGroup 附加屬性,這兩個附加屬性和在 WPF 中有一點不一樣的是不能放入在 ColumnDefinition 裡面。現實中我也確實沒有想到什麼辦法可以附加到 ColumnDefinition 裡面實現功能。這也就讓我仿造的功能比 WPF 弱

在後臺程式碼裡面的 Button_OnClick 只修改按鈕寬度,程式碼如下

    private void Button_OnClick(object sender, RoutedEventArgs e)
    {
        var button = (Button) sender;
        button.Width += 100;
    }

執行程式碼的介面效果如下圖

核心程式碼是 ColumnSharedSizeHelper 型別,其實現邏輯如下

public static class ColumnSharedSizeHelper
{
    // Copy From https://github.com/Qiu233/WinUISharedSizeGroup

    public static readonly DependencyProperty IsSharedSizeScopeProperty =
        DependencyProperty.RegisterAttached("IsSharedSizeScope", typeof(bool), typeof(UIElement), new PropertyMetadata(false));

    private static readonly DependencyProperty SharedSizeGroupProperty =
        DependencyProperty.RegisterAttached("SharedSizeGroup", typeof(string), typeof(UIElement), new PropertyMetadata(null));

    public static void SetIsSharedSizeScope(DependencyObject o, bool group) => o.SetValue(IsSharedSizeScopeProperty, group);
    public static bool GetIsSharedSizeScope(DependencyObject o) => (bool) o.GetValue(IsSharedSizeScopeProperty);

    public static void SetSharedSizeGroup(DependencyObject o, string group)
    {
        o.SetValue(SharedSizeGroupProperty, group);

        if (o is FrameworkElement framework)
        {
            framework.Loaded -= FrameworkOnLoaded;
            framework.Loaded += FrameworkOnLoaded;

            void FrameworkOnLoaded(object sender, RoutedEventArgs e)
            {
                TrySetSize(framework);

                framework.SizeChanged -= Framework_SizeChanged;
                framework.SizeChanged += Framework_SizeChanged;
            }
        }
    }

    private static void Framework_SizeChanged(object sender, SizeChangedEventArgs args)
    {
        if (sender is not FrameworkElement currentFrameworkElement)
        {
            return;
        }

        TrySetSize(currentFrameworkElement);
    }

    private static void TrySetSize(FrameworkElement currentFrameworkElement)
    {
        var sharedSizeGroup = GetSharedSizeGroup(currentFrameworkElement);

        if (string.IsNullOrEmpty(sharedSizeGroup))
        {
            return;
        }

        if (currentFrameworkElement.Parent is not Grid grid)
        {
            throw new InvalidOperationException();
        }

        FrameworkElement p = currentFrameworkElement;
        while (!ColumnSharedSizeHelper.GetIsSharedSizeScope(p))
        {
            if (VisualTreeHelper.GetParent(p) is not FrameworkElement fe)
            {
                return;
            }
            else
            {
                p = fe;
            }
        }

        if (p == currentFrameworkElement)
        {
            return;
        }

        if (!ColumnSharedSizeHelper.GetIsSharedSizeScope(p))
        {
            return;
        }

        var group = p.GetValue(GroupsProperty) as Dictionary<string, ColumnSharedSizeGroup>;
        if (group == null)
        {
            group = new Dictionary<string, ColumnSharedSizeGroup>();
            p.SetValue(GroupsProperty, group);
        }

        if (!group.TryGetValue(sharedSizeGroup, out var columnSharedSizeGroup))
        {
            columnSharedSizeGroup = new ColumnSharedSizeGroup();
            group.Add(sharedSizeGroup, columnSharedSizeGroup);
        }

        columnSharedSizeGroup.Update(currentFrameworkElement);
    }

    public static string GetSharedSizeGroup(DependencyObject o)
    {
        return (string) o.GetValue(SharedSizeGroupProperty);
    }

    public static readonly DependencyProperty GroupsProperty =
        DependencyProperty.RegisterAttached(nameof(ColumnSharedSizeGroup), typeof(Dictionary<string, ColumnSharedSizeGroup>), typeof(UIElement),
            new PropertyMetadata(null));

    class ColumnSharedSizeGroup
    {
        public void Update(FrameworkElement currentFrameworkElement)
        {
            var grid = (Grid) currentFrameworkElement.Parent;
            var value = (int) currentFrameworkElement.GetValue(Grid.ColumnProperty);

            var column = grid.ColumnDefinitions[value];
            if (!_columns.Contains(column))
            {
                _columns.Add(column);
            }
            var adjustments = new List<ColumnDefinition>();
            var width = currentFrameworkElement.ActualWidth + currentFrameworkElement.Margin.Left + currentFrameworkElement.Margin.Right;
            if (width > _columnSize)
            {
                _columnSize = width;
                adjustments.AddRange(_columns);
            }
            else
            {
                adjustments.Add(column);
            }

            foreach (var columnDefinition in adjustments)
            {
                columnDefinition.Width = new GridLength(_columnSize);
            }
        }

        private readonly List<ColumnDefinition> _columns = [];
        private double _columnSize = 0.0;
    }
}

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 48c6e653a28a5f5609738a288b9b34b31f37c18c

以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 48c6e653a28a5f5609738a288b9b34b31f37c18c

獲取程式碼之後,進入 UnoDemo/JeawehonawbuWhaikeregaryere 資料夾,即可獲取到原始碼

更多技術部落格,請參閱 部落格導航

相關文章